From 99b6181b1e6f03e3500abe98e13fbd6059048229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 4 Dec 2016 22:44:42 +0100 Subject: [PATCH 1/8] Complete library rewrite. See comments on #207 for details. --- Cargo.toml | 31 +- benches/common/macros.rs | 29 +- benches/construction.rs | 19 - benches/dmat.rs | 90 -- benches/{mat.rs => matrix.rs} | 14 +- benches/{quat.rs => quaternion.rs} | 14 +- benches/vec.rs | 119 --- benches/vector.rs | 43 + src/core/alias.rs | 110 ++ src/core/allocator.rs | 86 ++ src/core/cg.rs | 376 +++++++ src/core/componentwise.rs | 77 ++ src/core/constraint.rs | 55 + src/core/construction.rs | 643 +++++++++++ src/core/conversion.rs | 59 + src/core/coordinates.rs | 230 ++++ src/core/decompositions.rs | 373 +++++++ src/core/default_allocator.rs | 104 ++ src/core/determinant.rs | 56 + src/core/dimension.rs | 342 ++++++ src/core/helper.rs | 19 + src/core/inverse.rs | 202 ++++ src/core/iter.rs | 86 ++ src/core/matrix.rs | 1013 ++++++++++++++++++ src/core/matrix_alga.rs | 788 ++++++++++++++ src/core/matrix_array.rs | 187 ++++ src/core/matrix_slice.rs | 466 ++++++++ src/core/matrix_vec.rs | 171 +++ src/core/mod.rs | 41 + src/core/ops.rs | 462 ++++++++ src/core/properties.rs | 107 ++ src/core/scalar.rs | 9 + src/core/storage.rs | 176 +++ src/core/unit.rs | 165 +++ src/geometry/isometry.rs | 195 ++++ src/geometry/isometry_alga.rs | 198 ++++ src/geometry/isometry_alias.rs | 19 + src/geometry/isometry_construction.rs | 195 ++++ src/geometry/isometry_conversion.rs | 163 +++ src/geometry/isometry_ops.rs | 435 ++++++++ src/geometry/mod.rs | 87 ++ src/geometry/op_macros.rs | 188 ++++ src/geometry/orthographic.rs | 269 +++++ src/geometry/perspective.rs | 222 ++++ src/geometry/point.rs | 228 ++++ src/geometry/point_alga.rs | 83 ++ src/geometry/point_alias.rs | 20 + src/geometry/point_construction.rs | 127 +++ src/geometry/point_conversion.rs | 73 ++ src/geometry/point_coordinates.rs | 47 + src/geometry/point_ops.rs | 263 +++++ src/geometry/quaternion.rs | 455 ++++++++ src/geometry/quaternion_alga.rs | 382 +++++++ src/geometry/quaternion_alias.rs | 10 + src/geometry/quaternion_construction.rs | 344 ++++++ src/geometry/quaternion_conversion.rs | 212 ++++ src/geometry/quaternion_coordinates.rs | 34 + src/geometry/quaternion_ops.rs | 729 +++++++++++++ src/geometry/rotation.rs | 173 +++ src/geometry/rotation_alga.rs | 298 ++++++ src/geometry/rotation_alias.rs | 13 + src/geometry/rotation_construction.rs | 31 + src/geometry/rotation_conversion.rs | 195 ++++ src/geometry/rotation_ops.rs | 162 +++ src/geometry/rotation_specialization.rs | 377 +++++++ src/geometry/similarity.rs | 269 +++++ src/geometry/similarity_alga.rs | 184 ++++ src/geometry/similarity_alias.rs | 19 + src/geometry/similarity_construction.rs | 188 ++++ src/geometry/similarity_conversion.rs | 163 +++ src/geometry/similarity_ops.rs | 512 +++++++++ src/geometry/transform.rs | 299 ++++++ src/geometry/transform_alga.rs | 157 +++ src/geometry/transform_alias.rs | 16 + src/geometry/transform_construction.rs | 36 + src/geometry/transform_conversion.rs | 67 ++ src/geometry/transform_ops.rs | 608 +++++++++++ src/geometry/translation.rs | 142 +++ src/geometry/translation_alga.rs | 209 ++++ src/geometry/translation_alias.rs | 13 + src/geometry/translation_construction.rs | 86 ++ src/geometry/translation_conversion.rs | 159 +++ src/geometry/translation_ops.rs | 168 +++ src/geometry/unit_complex.rs | 115 ++ src/geometry/unit_complex_alga.rs | 188 ++++ src/geometry/unit_complex_construction.rs | 107 ++ src/geometry/unit_complex_ops.rs | 309 ++++++ src/lib.rs | 1102 +++++-------------- src/linalg/decompositions.rs | 351 ------ src/linalg/mod.rs | 4 - src/macros/assert.rs | 29 - src/macros/mod.rs | 1 - src/structs/algebra/dummy.rs | 17 - src/structs/algebra/matrix.rs | 25 - src/structs/algebra/mod.rs | 13 - src/structs/algebra/point.rs | 34 - src/structs/algebra/rotation.rs | 92 -- src/structs/algebra/vector.rs | 186 ---- src/structs/common_macros.rs | 478 --------- src/structs/dmatrix.rs | 226 ---- src/structs/dmatrix_macros.rs | 1186 --------------------- src/structs/dvector.rs | 164 --- src/structs/dvector_macros.rs | 243 ----- src/structs/isometry.rs | 106 -- src/structs/isometry_macros.rs | 481 --------- src/structs/matrix.rs | 290 ----- src/structs/matrix_macros.rs | 741 ------------- src/structs/mod.rs | 53 - src/structs/orthographic.rs | 357 ------- src/structs/perspective.rs | 276 ----- src/structs/point.rs | 141 --- src/structs/point_macros.rs | 202 ---- src/structs/quaternion.rs | 684 ------------ src/structs/rotation.rs | 345 ------ src/structs/rotation_macros.rs | 423 -------- src/structs/similarity.rs | 57 - src/structs/similarity_macros.rs | 439 -------- src/structs/specializations/complex.rs | 110 -- src/structs/specializations/identity.rs | 85 -- src/structs/specializations/matrix.rs | 289 ----- src/structs/specializations/primitives.rs | 170 --- src/structs/specializations/vector.rs | 307 ------ src/structs/unit.rs | 80 -- src/structs/vector.rs | 167 --- src/structs/vector_macros.rs | 726 ------------- src/structs/vectorn.rs | 124 --- src/structs/vectorn_macros.rs | 607 ----------- src/traits/axpy.rs | 38 + src/traits/geometry.rs | 316 ------ src/traits/mod.rs | 20 +- src/traits/operations.rs | 412 ------- src/traits/structure.rs | 441 -------- tests/arbitrary.rs | 64 -- tests/assert.rs | 79 -- tests/conversion.rs | 147 +++ tests/isometry.rs | 222 ++++ tests/mat.rs | 852 --------------- tests/matrix.rs | 631 +++++++++++ tests/matrix_slice.rs | 162 +++ tests/op_assign.rs | 76 -- tests/point.rs | 104 ++ tests/quat.rs | 121 --- tests/quaternion.rs | 247 +++++ tests/rotation.rs | 180 ++++ tests/similarity.rs | 274 +++++ tests/transforms.rs | 280 ----- tests/vec.rs | 374 ------- 147 files changed, 19022 insertions(+), 14432 deletions(-) delete mode 100644 benches/construction.rs delete mode 100644 benches/dmat.rs rename benches/{mat.rs => matrix.rs} (71%) rename benches/{quat.rs => quaternion.rs} (62%) delete mode 100644 benches/vec.rs create mode 100644 benches/vector.rs create mode 100644 src/core/alias.rs create mode 100644 src/core/allocator.rs create mode 100644 src/core/cg.rs create mode 100644 src/core/componentwise.rs create mode 100644 src/core/constraint.rs create mode 100644 src/core/construction.rs create mode 100644 src/core/conversion.rs create mode 100644 src/core/coordinates.rs create mode 100644 src/core/decompositions.rs create mode 100644 src/core/default_allocator.rs create mode 100644 src/core/determinant.rs create mode 100644 src/core/dimension.rs create mode 100644 src/core/helper.rs create mode 100644 src/core/inverse.rs create mode 100644 src/core/iter.rs create mode 100644 src/core/matrix.rs create mode 100644 src/core/matrix_alga.rs create mode 100644 src/core/matrix_array.rs create mode 100644 src/core/matrix_slice.rs create mode 100644 src/core/matrix_vec.rs create mode 100644 src/core/mod.rs create mode 100644 src/core/ops.rs create mode 100644 src/core/properties.rs create mode 100644 src/core/scalar.rs create mode 100644 src/core/storage.rs create mode 100644 src/core/unit.rs create mode 100644 src/geometry/isometry.rs create mode 100644 src/geometry/isometry_alga.rs create mode 100644 src/geometry/isometry_alias.rs create mode 100644 src/geometry/isometry_construction.rs create mode 100644 src/geometry/isometry_conversion.rs create mode 100644 src/geometry/isometry_ops.rs create mode 100644 src/geometry/mod.rs create mode 100644 src/geometry/op_macros.rs create mode 100644 src/geometry/orthographic.rs create mode 100644 src/geometry/perspective.rs create mode 100644 src/geometry/point.rs create mode 100644 src/geometry/point_alga.rs create mode 100644 src/geometry/point_alias.rs create mode 100644 src/geometry/point_construction.rs create mode 100644 src/geometry/point_conversion.rs create mode 100644 src/geometry/point_coordinates.rs create mode 100644 src/geometry/point_ops.rs create mode 100644 src/geometry/quaternion.rs create mode 100644 src/geometry/quaternion_alga.rs create mode 100644 src/geometry/quaternion_alias.rs create mode 100644 src/geometry/quaternion_construction.rs create mode 100644 src/geometry/quaternion_conversion.rs create mode 100644 src/geometry/quaternion_coordinates.rs create mode 100644 src/geometry/quaternion_ops.rs create mode 100644 src/geometry/rotation.rs create mode 100644 src/geometry/rotation_alga.rs create mode 100644 src/geometry/rotation_alias.rs create mode 100644 src/geometry/rotation_construction.rs create mode 100644 src/geometry/rotation_conversion.rs create mode 100644 src/geometry/rotation_ops.rs create mode 100644 src/geometry/rotation_specialization.rs create mode 100644 src/geometry/similarity.rs create mode 100644 src/geometry/similarity_alga.rs create mode 100644 src/geometry/similarity_alias.rs create mode 100644 src/geometry/similarity_construction.rs create mode 100644 src/geometry/similarity_conversion.rs create mode 100644 src/geometry/similarity_ops.rs create mode 100644 src/geometry/transform.rs create mode 100644 src/geometry/transform_alga.rs create mode 100644 src/geometry/transform_alias.rs create mode 100644 src/geometry/transform_construction.rs create mode 100644 src/geometry/transform_conversion.rs create mode 100644 src/geometry/transform_ops.rs create mode 100644 src/geometry/translation.rs create mode 100644 src/geometry/translation_alga.rs create mode 100644 src/geometry/translation_alias.rs create mode 100644 src/geometry/translation_construction.rs create mode 100644 src/geometry/translation_conversion.rs create mode 100644 src/geometry/translation_ops.rs create mode 100644 src/geometry/unit_complex.rs create mode 100644 src/geometry/unit_complex_alga.rs create mode 100644 src/geometry/unit_complex_construction.rs create mode 100644 src/geometry/unit_complex_ops.rs delete mode 100644 src/linalg/decompositions.rs delete mode 100644 src/linalg/mod.rs delete mode 100644 src/macros/assert.rs delete mode 100644 src/macros/mod.rs delete mode 100644 src/structs/algebra/dummy.rs delete mode 100644 src/structs/algebra/matrix.rs delete mode 100644 src/structs/algebra/mod.rs delete mode 100644 src/structs/algebra/point.rs delete mode 100644 src/structs/algebra/rotation.rs delete mode 100644 src/structs/algebra/vector.rs delete mode 100644 src/structs/common_macros.rs delete mode 100644 src/structs/dmatrix.rs delete mode 100644 src/structs/dmatrix_macros.rs delete mode 100644 src/structs/dvector.rs delete mode 100644 src/structs/dvector_macros.rs delete mode 100644 src/structs/isometry.rs delete mode 100644 src/structs/isometry_macros.rs delete mode 100644 src/structs/matrix.rs delete mode 100644 src/structs/matrix_macros.rs delete mode 100644 src/structs/mod.rs delete mode 100644 src/structs/orthographic.rs delete mode 100644 src/structs/perspective.rs delete mode 100644 src/structs/point.rs delete mode 100644 src/structs/point_macros.rs delete mode 100644 src/structs/quaternion.rs delete mode 100644 src/structs/rotation.rs delete mode 100644 src/structs/rotation_macros.rs delete mode 100644 src/structs/similarity.rs delete mode 100644 src/structs/similarity_macros.rs delete mode 100644 src/structs/specializations/complex.rs delete mode 100644 src/structs/specializations/identity.rs delete mode 100644 src/structs/specializations/matrix.rs delete mode 100644 src/structs/specializations/primitives.rs delete mode 100644 src/structs/specializations/vector.rs delete mode 100644 src/structs/unit.rs delete mode 100644 src/structs/vector.rs delete mode 100644 src/structs/vector_macros.rs delete mode 100644 src/structs/vectorn.rs delete mode 100644 src/structs/vectorn_macros.rs create mode 100644 src/traits/axpy.rs delete mode 100644 src/traits/geometry.rs delete mode 100644 src/traits/operations.rs delete mode 100644 src/traits/structure.rs delete mode 100644 tests/arbitrary.rs delete mode 100644 tests/assert.rs create mode 100644 tests/conversion.rs create mode 100644 tests/isometry.rs delete mode 100644 tests/mat.rs create mode 100644 tests/matrix.rs create mode 100644 tests/matrix_slice.rs delete mode 100644 tests/op_assign.rs create mode 100644 tests/point.rs delete mode 100644 tests/quat.rs create mode 100644 tests/quaternion.rs create mode 100644 tests/rotation.rs create mode 100644 tests/similarity.rs delete mode 100644 tests/transforms.rs delete mode 100644 tests/vec.rs diff --git a/Cargo.toml b/Cargo.toml index 22cc158b..37a43717 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nalgebra" version = "0.10.1" -authors = [ "Sébastien Crozet " ] # FIXME: add the contributors. +authors = [ "Sébastien Crozet " ] description = "Linear algebra library for computer physics, computer graphics and general low-dimensional linear algebra for Rust." documentation = "http://nalgebra.org/doc/nalgebra/index.html" @@ -16,28 +16,19 @@ name = "nalgebra" path = "src/lib.rs" [features] -# Generate arbitrary instances of nalgebra types for testing with quickcheck arbitrary = [ "quickcheck" ] -generic_sizes = [ "generic-array", "typenum" ] -abstract_algebra = [ "algebra" ] [dependencies] -rustc-serialize = "0.3.*" -rand = "0.3.*" -num = "0.1.*" - -[dependencies.generic-array] -optional = true -version = "0.2.*" - -[dependencies.typenum] -optional = true -version = "1.3.*" +rustc-serialize = "0.3" +typenum = "1.4" +generic-array = "0.2" +rand = "0.3" +num-traits = "0.1" +num-complex = "0.1" +approx = "0.1" +alga = "0.4" +# clippy = "*" [dependencies.quickcheck] optional = true -version = "0.2.*" - -[dependencies.algebra] -optional = true -version = "0.2.*" +version = "0.3" diff --git a/benches/common/macros.rs b/benches/common/macros.rs index edaee213..74b0e0cc 100644 --- a/benches/common/macros.rs +++ b/benches/common/macros.rs @@ -16,7 +16,30 @@ macro_rules! bench_binop( i = (i + 1) & (LEN - 1); unsafe { - test::black_box((*elems1.get_unchecked(i)).$binop(*elems2.get_unchecked(i))) + test::black_box(elems1.get_unchecked(i).$binop(*elems2.get_unchecked(i))) + } + }) + } + } +); + +macro_rules! bench_binop_ref( + ($name: ident, $t1: ty, $t2: ty, $binop: ident) => { + #[bench] + fn $name(bh: &mut Bencher) { + const LEN: usize = 1 << 13; + + let mut rng = IsaacRng::new_unseeded(); + + let elems1: Vec<$t1> = (0usize .. LEN).map(|_| rng.gen::<$t1>()).collect(); + let elems2: Vec<$t2> = (0usize .. LEN).map(|_| rng.gen::<$t2>()).collect(); + let mut i = 0; + + bh.iter(|| { + i = (i + 1) & (LEN - 1); + + unsafe { + test::black_box(elems1.get_unchecked(i).$binop(elems2.get_unchecked(i))) } }) } @@ -46,7 +69,7 @@ macro_rules! bench_binop_na( } ); -macro_rules! bench_unop( +macro_rules! bench_unop_na( ($name: ident, $t: ty, $unop: ident) => { #[bench] fn $name(bh: &mut Bencher) { @@ -68,7 +91,7 @@ macro_rules! bench_unop( } ); -macro_rules! bench_unop_self( +macro_rules! bench_unop( ($name: ident, $t: ty, $unop: ident) => { #[bench] fn $name(bh: &mut Bencher) { diff --git a/benches/construction.rs b/benches/construction.rs deleted file mode 100644 index cc4179d0..00000000 --- a/benches/construction.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![feature(test)] - -extern crate test; -extern crate rand; -extern crate nalgebra as na; - -use rand::{IsaacRng, Rng}; -use test::Bencher; -use na::{UnitQuaternion, Rotation2, Rotation3, Vector1, Vector3}; - -#[path="common/macros.rs"] -mod macros; - -bench_construction!(_bench_quaternion_from_axisangle, UnitQuaternion::from_scaled_axis, axisangle: Vector3); -bench_construction!(_bench_rot2_from_axisangle, Rotation2::new, axisangle: Vector1); -bench_construction!(_bench_rot3_from_axisangle, Rotation3::new, axisangle: Vector3); - -bench_construction!(_bench_quaternion_from_euler_angles, UnitQuaternion::from_euler_angles, roll: f32, pitch: f32, yaw: f32); -bench_construction!(_bench_rot3_from_euler_angles, Rotation3::from_euler_angles, roll: f32, pitch: f32, yaw: f32); diff --git a/benches/dmat.rs b/benches/dmat.rs deleted file mode 100644 index 2d2ea784..00000000 --- a/benches/dmat.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![feature(test)] - -extern crate test; -extern crate nalgebra as na; - -use test::Bencher; -use na::{DVector, DMatrix}; - -macro_rules! bench_mul_dmatrix( - ($bh: expr, $nrows: expr, $ncols: expr) => { - { - $bh.iter(|| { - let a: DMatrix = DMatrix::new_random($nrows, $ncols); - let mut b: DMatrix = DMatrix::new_random($nrows, $ncols); - - for _ in 0usize .. 1000 { - // XXX: the clone here is highly undesirable! - b = a.clone() * b; - } - }) - } - } -); - -#[bench] -fn bench_mul_dmat2(bh: &mut Bencher) { - bench_mul_dmatrix!(bh, 2, 2); -} - -#[bench] -fn bench_mul_dmat3(bh: &mut Bencher) { - bench_mul_dmatrix!(bh, 3, 3); -} - -#[bench] -fn bench_mul_dmat4(bh: &mut Bencher) { - bench_mul_dmatrix!(bh, 4, 4); -} - -#[bench] -fn bench_mul_dmat5(bh: &mut Bencher) { - bench_mul_dmatrix!(bh, 5, 5); -} - -#[bench] -fn bench_mul_dmat6(bh: &mut Bencher) { - bench_mul_dmatrix!(bh, 6, 6); -} - -macro_rules! bench_mul_dmat_dvector( - ($bh: expr, $nrows: expr, $ncols: expr) => { - { - - $bh.iter(|| { - let m : DMatrix = DMatrix::new_random($nrows, $ncols); - let mut v : DVector = DVector::new_random($ncols); - - for _ in 0usize .. 1000 { - // XXX: the clone here is highly undesirable! - v = m.clone() * v - } - }) - } - } -); - -#[bench] -fn bench_mul_dmat_dvec2(bh: &mut Bencher) { - bench_mul_dmat_dvector!(bh, 2, 2); -} - -#[bench] -fn bench_mul_dmat_dvec3(bh: &mut Bencher) { - bench_mul_dmat_dvector!(bh, 3, 3); -} - -#[bench] -fn bench_mul_dmat_dvec4(bh: &mut Bencher) { - bench_mul_dmat_dvector!(bh, 4, 4); -} - -#[bench] -fn bench_mul_dmat_dvec5(bh: &mut Bencher) { - bench_mul_dmat_dvector!(bh, 5, 5); -} - -#[bench] -fn bench_mul_dmat_dvec6(bh: &mut Bencher) { - bench_mul_dmat_dvector!(bh, 6, 6); -} diff --git a/benches/mat.rs b/benches/matrix.rs similarity index 71% rename from benches/mat.rs rename to benches/matrix.rs index 7fed7c84..8403b5b7 100644 --- a/benches/mat.rs +++ b/benches/matrix.rs @@ -16,6 +16,10 @@ bench_binop!(_bench_mat2_mul_m, Matrix2, Matrix2, mul); bench_binop!(_bench_mat3_mul_m, Matrix3, Matrix3, mul); bench_binop!(_bench_mat4_mul_m, Matrix4, Matrix4, mul); +bench_binop_ref!(_bench_mat2_tr_mul_m, Matrix2, Matrix2, tr_mul); +bench_binop_ref!(_bench_mat3_tr_mul_m, Matrix3, Matrix3, tr_mul); +bench_binop_ref!(_bench_mat4_tr_mul_m, Matrix4, Matrix4, tr_mul); + bench_binop!(_bench_mat2_add_m, Matrix2, Matrix2, add); bench_binop!(_bench_mat3_add_m, Matrix3, Matrix3, add); bench_binop!(_bench_mat4_add_m, Matrix4, Matrix4, add); @@ -28,6 +32,10 @@ bench_binop!(_bench_mat2_mul_v, Matrix2, Vector2, mul); bench_binop!(_bench_mat3_mul_v, Matrix3, Vector3, mul); bench_binop!(_bench_mat4_mul_v, Matrix4, Vector4, mul); +bench_binop_ref!(_bench_mat2_tr_mul_v, Matrix2, Vector2, tr_mul); +bench_binop_ref!(_bench_mat3_tr_mul_v, Matrix3, Vector3, tr_mul); +bench_binop_ref!(_bench_mat4_tr_mul_v, Matrix4, Vector4, tr_mul); + bench_binop!(_bench_mat2_mul_s, Matrix2, f32, mul); bench_binop!(_bench_mat3_mul_s, Matrix3, f32, mul); bench_binop!(_bench_mat4_mul_s, Matrix4, f32, mul); @@ -36,9 +44,9 @@ bench_binop!(_bench_mat2_div_s, Matrix2, f32, div); bench_binop!(_bench_mat3_div_s, Matrix3, f32, div); bench_binop!(_bench_mat4_div_s, Matrix4, f32, div); -bench_unop!(_bench_mat2_inv, Matrix2, inverse); -bench_unop!(_bench_mat3_inv, Matrix3, inverse); -bench_unop!(_bench_mat4_inv, Matrix4, inverse); +bench_unop!(_bench_mat2_inv, Matrix2, try_inverse); +bench_unop!(_bench_mat3_inv, Matrix3, try_inverse); +bench_unop!(_bench_mat4_inv, Matrix4, try_inverse); bench_unop!(_bench_mat2_transpose, Matrix2, transpose); bench_unop!(_bench_mat3_transpose, Matrix3, transpose); diff --git a/benches/quat.rs b/benches/quaternion.rs similarity index 62% rename from benches/quat.rs rename to benches/quaternion.rs index d69adc9e..f1c66ef5 100644 --- a/benches/quat.rs +++ b/benches/quaternion.rs @@ -15,10 +15,14 @@ mod macros; bench_binop!(_bench_quaternion_add_q, Quaternion, Quaternion, add); bench_binop!(_bench_quaternion_sub_q, Quaternion, Quaternion, sub); bench_binop!(_bench_quaternion_mul_q, Quaternion, Quaternion, mul); -// bench_binop!(_bench_quaternion_div_q, Quaternion, Quaternion, div) -bench_binop!(_bench_quaternion_mul_v, UnitQuaternion, Vector3, mul); + +bench_binop!(_bench_unit_quaternion_mul_v, UnitQuaternion, Vector3, mul); + bench_binop!(_bench_quaternion_mul_s, Quaternion, f32, mul); bench_binop!(_bench_quaternion_div_s, Quaternion, f32, div); -bench_unop!(_bench_quaternion_inv, Quaternion, inverse); -bench_unop_self!(_bench_quaternion_conjugate, Quaternion, conjugate); -bench_unop!(_bench_quaternion_normalize, Quaternion, normalize); + +bench_unop!(_bench_quaternion_inv, Quaternion, try_inverse); +bench_unop!(_bench_unit_quaternion_inv, UnitQuaternion, inverse); + +// bench_unop_self!(_bench_quaternion_conjugate, Quaternion, conjugate); +// bench_unop!(_bench_quaternion_normalize, Quaternion, normalize); diff --git a/benches/vec.rs b/benches/vec.rs deleted file mode 100644 index 4681dc1f..00000000 --- a/benches/vec.rs +++ /dev/null @@ -1,119 +0,0 @@ -#![feature(test)] - -#[cfg(feature = "generic_sizes")] -extern crate typenum; - -extern crate test; -extern crate rand; -extern crate nalgebra as na; - -use rand::{IsaacRng, Rng}; -use test::Bencher; -use na::{Vector2, Vector3, Vector4}; -use std::ops::{Add, Sub, Mul, Div}; - -#[path="common/macros.rs"] -mod macros; - -bench_binop!(_bench_vec2_add_v, Vector2, Vector2, add); -bench_binop!(_bench_vec3_add_v, Vector3, Vector3, add); -bench_binop!(_bench_vec4_add_v, Vector4, Vector4, add); - -bench_binop!(_bench_vec2_sub_v, Vector2, Vector2, sub); -bench_binop!(_bench_vec3_sub_v, Vector3, Vector3, sub); -bench_binop!(_bench_vec4_sub_v, Vector4, Vector4, sub); - -bench_binop!(_bench_vec2_mul_v, Vector2, Vector2, mul); -bench_binop!(_bench_vec3_mul_v, Vector3, Vector3, mul); -bench_binop!(_bench_vec4_mul_v, Vector4, Vector4, mul); - -bench_binop!(_bench_vec2_div_v, Vector2, Vector2, div); -bench_binop!(_bench_vec3_div_v, Vector3, Vector3, div); -bench_binop!(_bench_vec4_div_v, Vector4, Vector4, div); - -bench_binop!(_bench_vec2_add_s, Vector2, f32, add); -bench_binop!(_bench_vec3_add_s, Vector3, f32, add); -bench_binop!(_bench_vec4_add_s, Vector4, f32, add); - -bench_binop!(_bench_vec2_sub_s, Vector2, f32, sub); -bench_binop!(_bench_vec3_sub_s, Vector3, f32, sub); -bench_binop!(_bench_vec4_sub_s, Vector4, f32, sub); - -bench_binop!(_bench_vec2_mul_s, Vector2, f32, mul); -bench_binop!(_bench_vec3_mul_s, Vector3, f32, mul); -bench_binop!(_bench_vec4_mul_s, Vector4, f32, mul); - -bench_binop!(_bench_vec2_div_s, Vector2, f32, div); -bench_binop!(_bench_vec3_div_s, Vector3, f32, div); -bench_binop!(_bench_vec4_div_s, Vector4, f32, div); - -bench_binop_na!(_bench_vec2_dot, Vector2, Vector2, dot); -bench_binop_na!(_bench_vec3_dot, Vector3, Vector3, dot); -bench_binop_na!(_bench_vec4_dot, Vector4, Vector4, dot); - -bench_binop_na!(_bench_vec3_cross, Vector3, Vector3, cross); - -bench_unop!(_bench_vec2_norm, Vector2, norm); -bench_unop!(_bench_vec3_norm, Vector3, norm); -bench_unop!(_bench_vec4_norm, Vector4, norm); - -bench_unop!(_bench_vec2_normalize, Vector2, normalize); -bench_unop!(_bench_vec3_normalize, Vector3, normalize); -bench_unop!(_bench_vec4_normalize, Vector4, normalize); - -#[cfg(feature = "generic_sizes")] -mod bench_vecn { - extern crate test; - extern crate rand; - extern crate nalgebra as na; - - use rand::{IsaacRng, Rng}; - use test::Bencher; - use std::ops::{Add, Sub, Mul, Div}; - use typenum::{U2, U3, U4}; - use na::VectorN; - - bench_binop!(_bench_vecn2_add_v, VectorN, VectorN, add); - bench_binop!(_bench_vecn3_add_v, VectorN, VectorN, add); - bench_binop!(_bench_vecn4_add_v, VectorN, VectorN, add); - - bench_binop!(_bench_vecn2_sub_v, VectorN, VectorN, sub); - bench_binop!(_bench_vecn3_sub_v, VectorN, VectorN, sub); - bench_binop!(_bench_vecn4_sub_v, VectorN, VectorN, sub); - - bench_binop!(_bench_vecn2_mul_v, VectorN, VectorN, mul); - bench_binop!(_bench_vecn3_mul_v, VectorN, VectorN, mul); - bench_binop!(_bench_vecn4_mul_v, VectorN, VectorN, mul); - - bench_binop!(_bench_vecn2_div_v, VectorN, VectorN, div); - bench_binop!(_bench_vecn3_div_v, VectorN, VectorN, div); - bench_binop!(_bench_vecn4_div_v, VectorN, VectorN, div); - - bench_binop!(_bench_vecn2_add_s, VectorN, f32, add); - bench_binop!(_bench_vecn3_add_s, VectorN, f32, add); - bench_binop!(_bench_vecn4_add_s, VectorN, f32, add); - - bench_binop!(_bench_vecn2_sub_s, VectorN, f32, sub); - bench_binop!(_bench_vecn3_sub_s, VectorN, f32, sub); - bench_binop!(_bench_vecn4_sub_s, VectorN, f32, sub); - - bench_binop!(_bench_vecn2_mul_s, VectorN, f32, mul); - bench_binop!(_bench_vecn3_mul_s, VectorN, f32, mul); - bench_binop!(_bench_vecn4_mul_s, VectorN, f32, mul); - - bench_binop!(_bench_vecn2_div_s, VectorN, f32, div); - bench_binop!(_bench_vecn3_div_s, VectorN, f32, div); - bench_binop!(_bench_vecn4_div_s, VectorN, f32, div); - - bench_binop_na!(_bench_vecn2_dot, VectorN, VectorN, dot); - bench_binop_na!(_bench_vecn3_dot, VectorN, VectorN, dot); - bench_binop_na!(_bench_vecn4_dot, VectorN, VectorN, dot); - - bench_unop!(_bench_vecn2_norm, VectorN, norm); - bench_unop!(_bench_vecn3_norm, VectorN, norm); - bench_unop!(_bench_vecn4_norm, VectorN, norm); - - bench_unop!(_bench_vecn2_normalize, VectorN, normalize); - bench_unop!(_bench_vecn3_normalize, VectorN, normalize); - bench_unop!(_bench_vecn4_normalize, VectorN, normalize); -} diff --git a/benches/vector.rs b/benches/vector.rs new file mode 100644 index 00000000..aa897145 --- /dev/null +++ b/benches/vector.rs @@ -0,0 +1,43 @@ +#![feature(test)] + +extern crate test; +extern crate rand; +extern crate nalgebra as na; + +use rand::{IsaacRng, Rng}; +use test::Bencher; +use na::{Vector2, Vector3, Vector4}; +use std::ops::{Add, Sub, Mul, Div}; + +#[path="common/macros.rs"] +mod macros; + +bench_binop!(_bench_vec2_add_v, Vector2, Vector2, add); +bench_binop!(_bench_vec3_add_v, Vector3, Vector3, add); +bench_binop!(_bench_vec4_add_v, Vector4, Vector4, add); + +bench_binop!(_bench_vec2_sub_v, Vector2, Vector2, sub); +bench_binop!(_bench_vec3_sub_v, Vector3, Vector3, sub); +bench_binop!(_bench_vec4_sub_v, Vector4, Vector4, sub); + +bench_binop!(_bench_vec2_mul_s, Vector2, f32, mul); +bench_binop!(_bench_vec3_mul_s, Vector3, f32, mul); +bench_binop!(_bench_vec4_mul_s, Vector4, f32, mul); + +bench_binop!(_bench_vec2_div_s, Vector2, f32, div); +bench_binop!(_bench_vec3_div_s, Vector3, f32, div); +bench_binop!(_bench_vec4_div_s, Vector4, f32, div); + +bench_binop_ref!(_bench_vec2_dot, Vector2, Vector2, dot); +bench_binop_ref!(_bench_vec3_dot, Vector3, Vector3, dot); +bench_binop_ref!(_bench_vec4_dot, Vector4, Vector4, dot); + +bench_binop_ref!(_bench_vec3_cross, Vector3, Vector3, cross); + +bench_unop!(_bench_vec2_norm, Vector2, norm); +bench_unop!(_bench_vec3_norm, Vector3, norm); +bench_unop!(_bench_vec4_norm, Vector4, norm); + +bench_unop!(_bench_vec2_normalize, Vector2, normalize); +bench_unop!(_bench_vec3_normalize, Vector3, normalize); +bench_unop!(_bench_vec4_normalize, Vector4, normalize); diff --git a/src/core/alias.rs b/src/core/alias.rs new file mode 100644 index 00000000..e4395dcd --- /dev/null +++ b/src/core/alias.rs @@ -0,0 +1,110 @@ +use core::Matrix; +use core::dimension::{Dynamic, U1, U2, U3, U4, U5, U6}; +use core::matrix_array::MatrixArray; +use core::matrix_vec::MatrixVec; + +/* + * + * + * Column-major matrices. + * + * + */ +/// A dynamically sized column-major matrix. +pub type DMatrix = Matrix>; + +/// A staticaly sized column-major matrix with `R` rows and `C` columns. +pub type MatrixNM = Matrix>; + +/// A staticaly sized column-major square matrix with `D` rows and columns. +pub type MatrixN = MatrixNM; + +pub type Matrix1 = MatrixN; +pub type Matrix2 = MatrixN; +pub type Matrix3 = MatrixN; +pub type Matrix4 = MatrixN; +pub type Matrix5 = MatrixN; +pub type Matrix6 = MatrixN; + +pub type Matrix1x2 = MatrixNM; +pub type Matrix1x3 = MatrixNM; +pub type Matrix1x4 = MatrixNM; +pub type Matrix1x5 = MatrixNM; +pub type Matrix1x6 = MatrixNM; + +pub type Matrix2x3 = MatrixNM; +pub type Matrix2x4 = MatrixNM; +pub type Matrix2x5 = MatrixNM; +pub type Matrix2x6 = MatrixNM; + +pub type Matrix3x4 = MatrixNM; +pub type Matrix3x5 = MatrixNM; +pub type Matrix3x6 = MatrixNM; + +pub type Matrix4x5 = MatrixNM; +pub type Matrix4x6 = MatrixNM; + +pub type Matrix5x6 = MatrixNM; + + +pub type Matrix2x1 = MatrixNM; +pub type Matrix3x1 = MatrixNM; +pub type Matrix4x1 = MatrixNM; +pub type Matrix5x1 = MatrixNM; +pub type Matrix6x1 = MatrixNM; + +pub type Matrix3x2 = MatrixNM; +pub type Matrix4x2 = MatrixNM; +pub type Matrix5x2 = MatrixNM; +pub type Matrix6x2 = MatrixNM; + +pub type Matrix4x3 = MatrixNM; +pub type Matrix5x3 = MatrixNM; +pub type Matrix6x3 = MatrixNM; + +pub type Matrix5x4 = MatrixNM; +pub type Matrix6x4 = MatrixNM; + +pub type Matrix6x5 = MatrixNM; + + +/* + * + * + * Column vectors. + * + * + */ +/// A dynamically sized column vector. +pub type DVector = Matrix>; + +/// A statically sized D-dimensional column vector. +pub type VectorN = MatrixNM; + +pub type Vector1 = VectorN; +pub type Vector2 = VectorN; +pub type Vector3 = VectorN; +pub type Vector4 = VectorN; +pub type Vector5 = VectorN; +pub type Vector6 = VectorN; + + +/* + * + * + * Row vectors. + * + * + */ +/// A dynamically sized row vector. +pub type RowDVector = Matrix>; + +/// A statically sized D-dimensional row vector. +pub type RowVectorN = MatrixNM; + +pub type RowVector1 = RowVectorN; +pub type RowVector2 = RowVectorN; +pub type RowVector3 = RowVectorN; +pub type RowVector4 = RowVectorN; +pub type RowVector5 = RowVectorN; +pub type RowVector6 = RowVectorN; diff --git a/src/core/allocator.rs b/src/core/allocator.rs new file mode 100644 index 00000000..23bfd356 --- /dev/null +++ b/src/core/allocator.rs @@ -0,0 +1,86 @@ +use std::any::Any; + +use core::Scalar; +use core::constraint::{SameNumberOfRows, SameNumberOfColumns, ShapeConstraint}; +use core::dimension::{Dim, U1}; +use core::storage::{Storage, OwnedStorage}; + +/// A matrix allocator of a memory buffer that may contain `R::to_usize() * C::to_usize()` +/// elements of type `N`. +/// +/// An allocator is said to be: +/// − static: if `R` and `C` both implement `DimName`. +/// − dynamic: if either one (or both) of `R` or `C` is equal to `Dynamic`. +/// +/// Every allocator must be both static and dynamic. Though not all implementations may share the +/// same `Buffer` type. +pub trait Allocator: Any + Sized { + type Buffer: OwnedStorage; + + /// Allocates a buffer with the given number of rows and columns without initializing its content. + unsafe fn allocate_uninitialized(nrows: R, ncols: C) -> Self::Buffer; + + /// Allocates a buffer initialized with the content of the given iterator. + fn allocate_from_iterator>(nrows: R, ncols: C, iter: I) -> Self::Buffer; +} + +/// A matrix data allocator dedicated to the given owned matrix storage. +pub trait OwnedAllocator>: + Allocator { +} + +impl OwnedAllocator for T + where N: Scalar, R: Dim, C: Dim, + T: Allocator, + S: OwnedStorage { +} + +/// The number of rows of the result of a componentwise operation on two matrices. +pub type SameShapeR = >::Representative; + +/// The number of columns of the result of a componentwise operation on two matrices. +pub type SameShapeC = >::Representative; + +// FIXME: Bad name. +/// Restricts the given number of rows and columns to be respectively the same. Can only be used +/// when `Self = SA::Alloc`. +pub trait SameShapeAllocator: + Allocator + + Allocator, SameShapeC> + where R1: Dim, R2: Dim, C1: Dim, C2: Dim, + N: Scalar, + SA: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { +} + +impl SameShapeAllocator for SA::Alloc + where R1: Dim, R2: Dim, C1: Dim, C2: Dim, + N: Scalar, + SA: Storage, + SA::Alloc: + Allocator + + Allocator, SameShapeC>, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { +} + +// XXX: Bad name. +/// Restricts the given number of rows to be equal. Can only be used when `Self = SA::Alloc`. +pub trait SameShapeColumnVectorAllocator: + Allocator + + Allocator, U1> + + SameShapeAllocator + where R1: Dim, R2: Dim, + N: Scalar, + SA: Storage, + ShapeConstraint: SameNumberOfRows { +} + +impl SameShapeColumnVectorAllocator for SA::Alloc + where R1: Dim, R2: Dim, + N: Scalar, + SA: Storage, + SA::Alloc: + Allocator + + Allocator, U1>, + ShapeConstraint: SameNumberOfRows { +} diff --git a/src/core/cg.rs b/src/core/cg.rs new file mode 100644 index 00000000..cfd40ac3 --- /dev/null +++ b/src/core/cg.rs @@ -0,0 +1,376 @@ +/* + * + * Computer-graphics specific implementations. + * Currently, it is mostly implemented for homogeneous matrices in 2- and 3-space. + * + */ + +use num::One; + +use core::{Scalar, SquareMatrix, OwnedSquareMatrix, ColumnVector, Unit}; +use core::dimension::{DimName, DimNameSub, DimNameDiff, U1, U2, U3, U4}; +use core::storage::{Storage, StorageMut, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; +use geometry::{PointBase, OrthographicBase, PerspectiveBase, IsometryBase, OwnedRotation, OwnedPoint}; + +use alga::general::{Real, Field}; +use alga::linear::Transformation; + + +impl SquareMatrix + where N: Scalar + Field, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new homogeneous matrix that applies the same scaling factor on each dimension. + #[inline] + pub fn new_scaling(scaling: N) -> Self { + let mut res = Self::from_diagonal_element(scaling); + res[(D::dim(), D::dim())] = N::one(); + + res + } + + /// Creates a new homogeneous matrix that applies a distinct scaling factor for each dimension. + #[inline] + pub fn new_nonuniform_scaling(scaling: &ColumnVector, SB>) -> Self + where D: DimNameSub, + SB: Storage, U1> { + let mut res = Self::one(); + for i in 0 .. scaling.len() { + res[(i, i)] = scaling[i]; + } + + res + } + + /// Creates a new homogeneous matrix that applies a pure translation. + #[inline] + pub fn new_translation(translation: &ColumnVector, SB>) -> Self + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator, U1> { + let mut res = Self::one(); + res.fixed_slice_mut::, U1>(0, D::dim()).copy_from(translation); + + res + } +} + +impl SquareMatrix + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Builds a 2 dimensional homogeneous rotation matrix from an angle in radian. + #[inline] + pub fn new_rotation(angle: N) -> Self + where S::Alloc: Allocator { + OwnedRotation::::new(angle).to_homogeneous() + } +} + +impl SquareMatrix + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + + /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). + /// + /// Returns the identity matrix if the given argument is zero. + #[inline] + pub fn new_rotation(axisangle: ColumnVector) -> Self + where SB: Storage, + S::Alloc: Allocator { + OwnedRotation::::new(axisangle).to_homogeneous() + } + + /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). + /// + /// Returns the identity matrix if the given argument is zero. + #[inline] + pub fn new_rotation_wrt_point(axisangle: ColumnVector, pt: OwnedPoint) -> Self + where SB: Storage, + S::Alloc: Allocator + + Allocator + + Allocator { + let rot = OwnedRotation::::from_scaled_axis(axisangle); + IsometryBase::rotation_wrt_point(rot, pt).to_homogeneous() + } + + /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). + /// + /// Returns the identity matrix if the given argument is zero. + /// This is identical to `Self::new_rotation`. + #[inline] + pub fn from_scaled_axis(axisangle: ColumnVector) -> Self + where SB: Storage, + S::Alloc: Allocator { + OwnedRotation::::from_scaled_axis(axisangle).to_homogeneous() + } + + /// Creates a new rotation from Euler angles. + /// + /// The primitive rotations are applied in order: 1 roll − 2 pitch − 3 yaw. + pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> Self + where S::Alloc: Allocator { + OwnedRotation::::from_euler_angles(roll, pitch, yaw).to_homogeneous() + } + + /// Builds a 3D homogeneous rotation matrix from an axis and a rotation angle. + pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self + where SB: Storage, + S::Alloc: Allocator { + OwnedRotation::::from_axis_angle(axis, angle).to_homogeneous() + } + + /// Creates a new homogeneous matrix for an orthographic projection. + #[inline] + pub fn new_orthographic(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> Self { + OrthographicBase::new(left, right, bottom, top, znear, zfar).unwrap() + } + + /// Creates a new homogeneous matrix for a perspective projection. + #[inline] + pub fn new_perspective(aspect: N, fovy: N, znear: N, zfar: N) -> Self { + PerspectiveBase::new(aspect, fovy, znear, zfar).unwrap() + } + + /// Creates an isometry that corresponds to the local frame of an observer standing at the + /// point `eye` and looking toward `target`. + /// + /// It maps the view direction `target - eye` to the positive `z` axis and the origin to the + /// `eye`. + #[inline] + pub fn new_observer_frame(eye: &PointBase, + target: &PointBase, + up: &ColumnVector) + -> Self + where SB: OwnedStorage, + SB::Alloc: OwnedAllocator + + Allocator + + Allocator { + IsometryBase::> + ::new_observer_frame(eye, target, up).to_homogeneous() + } + + /// Builds a right-handed look-at view matrix. + #[inline] + pub fn look_at_rh(eye: &PointBase, + target: &PointBase, + up: &ColumnVector) + -> Self + where SB: OwnedStorage, + SB::Alloc: OwnedAllocator + + Allocator + + Allocator { + IsometryBase::> + ::look_at_rh(eye, target, up).to_homogeneous() + } + + /// Builds a left-handed look-at view matrix. + #[inline] + pub fn look_at_lh(eye: &PointBase, + target: &PointBase, + up: &ColumnVector) + -> Self + where SB: OwnedStorage, + SB::Alloc: OwnedAllocator + + Allocator + + Allocator { + IsometryBase::> + ::look_at_lh(eye, target, up).to_homogeneous() + } +} + + +impl SquareMatrix + where N: Scalar + Field, + S: Storage { + + /// Computes the transformation equal to `self` followed by an uniform scaling factor. + #[inline] + pub fn append_scaling(&self, scaling: N) -> OwnedSquareMatrix + where D: DimNameSub, + S::Alloc: Allocator, D> { + let mut res = self.clone_owned(); + res.append_scaling_mut(scaling); + res + } + + /// Computes the transformation equal to an uniform scaling factor followed by `self`. + #[inline] + pub fn prepend_scaling(&self, scaling: N) -> OwnedSquareMatrix + where D: DimNameSub, + S::Alloc: Allocator> { + let mut res = self.clone_owned(); + res.prepend_scaling_mut(scaling); + res + } + + /// Computes the transformation equal to `self` followed by a non-uniform scaling factor. + #[inline] + pub fn append_nonuniform_scaling(&self, scaling: &ColumnVector, SB>) + -> OwnedSquareMatrix + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator { + let mut res = self.clone_owned(); + res.append_nonuniform_scaling_mut(scaling); + res + } + + /// Computes the transformation equal to a non-uniform scaling factor followed by `self`. + #[inline] + pub fn prepend_nonuniform_scaling(&self, scaling: &ColumnVector, SB>) + -> OwnedSquareMatrix + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator { + let mut res = self.clone_owned(); + res.prepend_nonuniform_scaling_mut(scaling); + res + } + + /// Computes the transformation equal to `self` followed by a translation. + #[inline] + pub fn append_translation(&self, shift: &ColumnVector, SB>) + -> OwnedSquareMatrix + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator, U1> { + let mut res = self.clone_owned(); + res.append_translation_mut(shift); + res + } + + /// Computes the transformation equal to a translation followed by `self`. + #[inline] + pub fn prepend_translation(&self, shift: &ColumnVector, SB>) + -> OwnedSquareMatrix + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator, U1> + + Allocator, DimNameDiff> + + Allocator> { + let mut res = self.clone_owned(); + res.prepend_translation_mut(shift); + res + } +} + +impl SquareMatrix + where N: Scalar + Field, + S: StorageMut { + + /// Computes in-place the transformation equal to `self` followed by an uniform scaling factor. + #[inline] + pub fn append_scaling_mut(&mut self, scaling: N) + where D: DimNameSub, + S::Alloc: Allocator, D> { + let mut to_scale = self.fixed_rows_mut::>(0); + to_scale *= scaling; + } + + /// Computes in-place the transformation equal to an uniform scaling factor followed by `self`. + #[inline] + pub fn prepend_scaling_mut(&mut self, scaling: N) + where D: DimNameSub, + S::Alloc: Allocator> { + let mut to_scale = self.fixed_columns_mut::>(0); + to_scale *= scaling; + } + + /// Computes in-place the transformation equal to `self` followed by a non-uniform scaling factor. + #[inline] + pub fn append_nonuniform_scaling_mut(&mut self, scaling: &ColumnVector, SB>) + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator { + for i in 0 .. scaling.len() { + let mut to_scale = self.fixed_rows_mut::(i); + to_scale *= scaling[i]; + } + } + + /// Computes in-place the transformation equal to a non-uniform scaling factor followed by `self`. + #[inline] + pub fn prepend_nonuniform_scaling_mut(&mut self, scaling: &ColumnVector, SB>) + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator { + for i in 0 .. scaling.len() { + let mut to_scale = self.fixed_columns_mut::(i); + to_scale *= scaling[i]; + } + } + + /// Computes the transformation equal to `self` followed by a translation. + #[inline] + pub fn append_translation_mut(&mut self, shift: &ColumnVector, SB>) + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator, U1> { + for i in 0 .. D::dim() { + for j in 0 .. D::dim() - 1 { + self[(j, i)] += shift[i] * self[(D::dim(), j)]; + } + } + } + + /// Computes the transformation equal to a translation followed by `self`. + #[inline] + pub fn prepend_translation_mut(&mut self, shift: &ColumnVector, SB>) + where D: DimNameSub, + SB: Storage, U1>, + S::Alloc: Allocator, U1> + + Allocator, DimNameDiff> + + Allocator> { + let scale = self.fixed_slice::>(D::dim(), 0).tr_dot(&shift); + let post_translation = self.fixed_slice::, DimNameDiff>(0, 0) * shift; + + self[(D::dim(), D::dim())] += scale; + + let mut translation = self.fixed_slice_mut::, U1>(0, D::dim()); + translation += post_translation; + } +} + + +impl Transformation, SB>> for SquareMatrix + where N: Real, + D: DimNameSub, + SA: OwnedStorage, + SB: OwnedStorage, U1, Alloc = SA::Alloc>, + SA::Alloc: OwnedAllocator + + Allocator, DimNameDiff> + + Allocator, U1> + + Allocator>, + SB::Alloc: OwnedAllocator, U1, SB> { + #[inline] + fn transform_vector(&self, v: &ColumnVector, SB>) + -> ColumnVector, SB> { + let transform = self.fixed_slice::, DimNameDiff>(0, 0); + let normalizer = self.fixed_slice::>(D::dim(), 0); + let n = normalizer.tr_dot(&v); + + if !n.is_zero() { + return transform * (v / n); + } + + transform * v + } + + #[inline] + fn transform_point(&self, pt: &PointBase, SB>) + -> PointBase, SB> { + let transform = self.fixed_slice::, DimNameDiff>(0, 0); + let translation = self.fixed_slice::, U1>(0, D::dim()); + let normalizer = self.fixed_slice::>(D::dim(), 0); + let n = normalizer.tr_dot(&pt.coords) + unsafe { *self.get_unchecked(D::dim(), D::dim()) }; + + if !n.is_zero() { + return transform * (pt / n) + translation; + } + + transform * pt + translation + } +} diff --git a/src/core/componentwise.rs b/src/core/componentwise.rs new file mode 100644 index 00000000..b839f959 --- /dev/null +++ b/src/core/componentwise.rs @@ -0,0 +1,77 @@ +// Non-convensional componentwise operators. + +use num::Signed; + +use alga::general::{ClosedMul, ClosedDiv}; + +use core::{Scalar, Matrix, OwnedMatrix, MatrixSum}; +use core::dimension::Dim; +use core::storage::{Storage, StorageMut}; +use core::allocator::SameShapeAllocator; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; + + +/// The type of the result of a matrix componentwise operation. +pub type MatrixComponentOp = MatrixSum; + +impl> Matrix { + /// Computes the componentwise absolute value. + #[inline] + pub fn abs(&self) -> OwnedMatrix + where N: Signed { + let mut res = self.clone_owned(); + + for e in res.iter_mut() { + *e = e.abs(); + } + + res + } + + // FIXME: add other operators like component_ln, component_pow, etc. ? +} + +macro_rules! component_binop_impl( + ($($binop: ident, $binop_mut: ident, $Trait: ident . $binop_assign: ident);* $(;)*) => {$( + impl> Matrix { + /// Componentwise matrix multiplication. + #[inline] + pub fn $binop(&self, rhs: &Matrix) -> MatrixComponentOp + where N: $Trait, + R2: Dim, C2: Dim, + SB: Storage, + S::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + let mut res = self.clone_owned_sum(); + + for (res, rhs) in res.iter_mut().zip(rhs.iter()) { + res.$binop_assign(*rhs); + } + + res + } + } + + impl> Matrix { + /// Componentwise matrix multiplication. + #[inline] + pub fn $binop_mut(&mut self, rhs: &Matrix) + where N: $Trait, + R2: Dim, + C2: Dim, + SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + for (me, rhs) in self.iter_mut().zip(rhs.iter()) { + me.$binop_assign(*rhs); + } + } + } + )*} +); + +component_binop_impl!( + component_mul, component_mul_mut, ClosedMul.mul_assign; + component_div, component_div_mut, ClosedDiv.div_assign; + // FIXME: add other operators like bitshift, etc. ? +); diff --git a/src/core/constraint.rs b/src/core/constraint.rs new file mode 100644 index 00000000..182bf2ae --- /dev/null +++ b/src/core/constraint.rs @@ -0,0 +1,55 @@ +use core::dimension::{Dim, DimName, Dynamic}; + +/// A type for enforcing constraints. +pub struct ShapeConstraint; + +/// Constraints `C1` and `R2` to be equivalent. +pub trait AreMultipliable { +} + + +impl AreMultipliable for ShapeConstraint +where ShapeConstraint: DimEq { +} + +macro_rules! equality_trait_decl( + ($($Trait: ident),* $(,)*) => {$( + // XXX: we can't do something like `DimEq for D2` because we would require a blancket impl… + pub trait $Trait { + type Representative: Dim; + } + + impl $Trait for ShapeConstraint { + type Representative = D; + } + + impl $Trait for ShapeConstraint { + type Representative = D; + } + + impl $Trait for ShapeConstraint { + type Representative = D; + } + )*} +); + +equality_trait_decl!(DimEq, SameNumberOfRows, SameNumberOfColumns); + +/// Constraints D1 and D2 to be equivalent, where the both designates dimensions of algebraic +/// entities (e.g. square matrices). +pub trait SameDimension: SameNumberOfRows + SameNumberOfColumns { + type Representative: Dim; +} + +impl SameDimension for ShapeConstraint { + type Representative = D; +} + +impl SameDimension for ShapeConstraint { + type Representative = D; +} + +impl SameDimension for ShapeConstraint { + type Representative = D; +} diff --git a/src/core/construction.rs b/src/core/construction.rs new file mode 100644 index 00000000..db586a6c --- /dev/null +++ b/src/core/construction.rs @@ -0,0 +1,643 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use std::iter; +use num::{Zero, One, Bounded}; +use rand::{self, Rand, Rng}; +use typenum::{self, Cmp, Greater}; + +use alga::general::{ClosedAdd, ClosedMul}; + +use core::{Scalar, Matrix, SquareMatrix, ColumnVector, Unit}; +use core::dimension::{Dim, DimName, Dynamic, U1, U2, U3, U4, U5, U6}; +use core::allocator::{Allocator, OwnedAllocator}; +use core::storage::{Storage, OwnedStorage}; + +/* + * + * Generic constructors. + * + */ +impl> Matrix + // XXX: needed because of a compiler bug. See the rust compiler issue #26026. + where S::Alloc: OwnedAllocator { + /// Creates a new uninitialized matrix. If the matrix has a compile-time dimension, this panics + /// if `nrows != R::to_usize()` or `ncols != C::to_usize()`. + #[inline] + pub unsafe fn new_uninitialized_generic(nrows: R, ncols: C) -> Matrix { + Matrix::from_data(S::Alloc::allocate_uninitialized(nrows, ncols)) + } + + /// Creates a matrix with all its elements set to `elem`. + #[inline] + pub fn from_element_generic(nrows: R, ncols: C, elem: N) -> Matrix { + let len = nrows.value() * ncols.value(); + Matrix::from_iterator_generic(nrows, ncols, iter::repeat(elem).take(len)) + } + + /// Creates a matrix with all its elements filled by an iterator. + #[inline] + pub fn from_iterator_generic(nrows: R, ncols: C, iter: I) -> Matrix + where I: IntoIterator { + Matrix::from_data(S::Alloc::allocate_from_iterator(nrows, ncols, iter)) + } + + /// Creates a matrix with its elements filled with the components provided by a slice in + /// row-major order. + /// + /// The order of elements in the slice must follow the usual mathematic writing, i.e., + /// row-by-row. + #[inline] + pub fn from_row_slice_generic(nrows: R, ncols: C, slice: &[N]) -> Matrix { + assert!(slice.len() == nrows.value() * ncols.value(), + "Matrix init. error: the slice did not contain the right number of elements."); + + let mut res = unsafe { Self::new_uninitialized_generic(nrows, ncols) }; + let mut iter = slice.iter(); + + for i in 0 .. nrows.value() { + for j in 0 .. ncols.value() { + unsafe { + *res.get_unchecked_mut(i, j) = *iter.next().unwrap() + } + } + } + + res + } + + /// Creates a matrix with its elements filled with the components provided by a slice. The + /// components must have the same layout as the matrix data storage (i.e. row-major or column-major). + #[inline] + pub fn from_column_slice_generic(nrows: R, ncols: C, slice: &[N]) -> Matrix { + Matrix::from_iterator_generic(nrows, ncols, slice.iter().cloned()) + } + + /// Creates a matrix filled with the results of a function applied to each of its component + /// coordinates. + #[inline] + pub fn from_fn_generic(nrows: R, ncols: C, mut f: F) -> Matrix + where F: FnMut(usize, usize) -> N { + let mut res = unsafe { Self::new_uninitialized_generic(nrows, ncols) }; + + for i in 0 .. nrows.value() { + for j in 0 .. ncols.value() { + unsafe { *res.get_unchecked_mut(i, j) = f(i, j) } + } + } + + res + } + + /// Creates a new indentity matrix. + /// + /// If the matrix is not square, the largest square submatrix starting at index `(0, 0)` is set + /// to the identity matrix. All other entries are set to zero. + #[inline] + pub fn identity_generic(nrows: R, ncols: C) -> Matrix + where N: Zero + One { + Self::from_diagonal_element_generic(nrows, ncols, N::one()) + } + + /// Creates a new matrix with its diagonal filled with copies of `elt`. + /// + /// If the matrix is not square, the largest square submatrix starting at index `(0, 0)` is set + /// to the identity matrix. All other entries are set to zero. + #[inline] + pub fn from_diagonal_element_generic(nrows: R, ncols: C, elt: N) -> Matrix + where N: Zero + One { + let mut res = unsafe { Self::new_uninitialized_generic(nrows, ncols) }; + res.fill(N::zero()); + + for i in 0 .. ::min(nrows.value(), ncols.value()) { + unsafe { *res.get_unchecked_mut(i, i) = elt } + } + + res + } + + #[inline] + pub fn from_rows(rows: &[Matrix]) -> Matrix + where SB: Storage { + + assert!(rows.len() > 0, "At least one row must be given."); + let nrows = R::try_to_usize().unwrap_or(rows.len()); + let ncols = rows[0].len(); + assert!(rows.len() == nrows, "Invalid number of rows provided to build this matrix."); + + if C::try_to_usize().is_none() { + assert!(rows.iter().all(|r| r.len() == ncols), + "The rows provided must all have the same dimension."); + } + + // FIXME: optimize that. + Self::from_fn_generic(R::from_usize(nrows), C::from_usize(ncols), |i, j| rows[i][(0, j)]) + } + + #[inline] + pub fn from_columns(columns: &[ColumnVector]) -> Matrix + where SB: Storage { + + assert!(columns.len() > 0, "At least one column must be given."); + let ncols = C::try_to_usize().unwrap_or(columns.len()); + let nrows = columns[0].len(); + assert!(columns.len() == ncols, "Invalid number of columns provided to build this matrix."); + + if R::try_to_usize().is_none() { + assert!(columns.iter().all(|r| r.len() == nrows), + "The columns provided must all have the same dimension."); + } + + // FIXME: optimize that. + Self::from_fn_generic(R::from_usize(nrows), C::from_usize(ncols), |i, j| columns[j][i]) + } +} + +impl Matrix + where N: Scalar + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a matrix filled with random values. + #[inline] + pub fn new_random_generic(nrows: R, ncols: C) -> Matrix { + Matrix::from_fn_generic(nrows, ncols, |_, _| rand::random()) + } +} + +impl SquareMatrix + where N: Scalar + Zero, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a square matrix with its diagonal set to `diag` and all other entries set to 0. + #[inline] + pub fn from_diagonal>(diag: &ColumnVector) -> Self { + let (dim, _) = diag.data.shape(); + let mut res = Self::from_element_generic(dim, dim, N::zero()); + + for i in 0 .. diag.len() { + unsafe { *res.get_unchecked_mut(i, i) = *diag.get_unchecked(i, 0); } + } + + res + } +} + + +/* + * + * Generate constructors with varying number of arguments, depending on the object type. + * + */ +macro_rules! impl_constructors( + ($($Dims: ty),*; $(=> $DimIdent: ident: $DimBound: ident),*; $($gargs: expr),*; $($args: ident),*) => { + impl Matrix + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + + /// Creates a new uninitialized matrix. + #[inline] + pub unsafe fn new_uninitialized($($args: usize),*) -> Matrix { + Self::new_uninitialized_generic($($gargs),*) + } + + /// Creates a matrix with all its elements set to `elem`. + #[inline] + pub fn from_element($($args: usize,)* elem: N) -> Matrix { + Self::from_element_generic($($gargs, )* elem) + } + + /// Creates a matrix with all its elements filled by an iterator. + #[inline] + pub fn from_iterator($($args: usize,)* iter: I) -> Matrix + where I: IntoIterator { + Self::from_iterator_generic($($gargs, )* iter) + } + + /// Creates a matrix with its elements filled with the components provided by a slice + /// in row-major order. + /// + /// The order of elements in the slice must follow the usual mathematic writing, i.e., + /// row-by-row. + #[inline] + pub fn from_row_slice($($args: usize,)* slice: &[N]) -> Matrix { + Self::from_row_slice_generic($($gargs, )* slice) + } + + /// Creates a matrix with its elements filled with the components provided by a slice + /// in column-major order. + #[inline] + pub fn from_column_slice($($args: usize,)* slice: &[N]) -> Matrix { + Self::from_column_slice_generic($($gargs, )* slice) + } + + /// Creates a matrix filled with the results of a function applied to each of its + /// component coordinates. + // FIXME: don't take a dimension of the matrix is statically sized. + #[inline] + pub fn from_fn($($args: usize,)* f: F) -> Matrix + where F: FnMut(usize, usize) -> N { + Self::from_fn_generic($($gargs, )* f) + } + + #[inline] + pub fn identity($($args: usize,)*) -> Matrix + where N: Zero + One { + Self::identity_generic($($gargs),* ) + } + + #[inline] + pub fn from_diagonal_element($($args: usize,)* elt: N) -> Matrix + where N: Zero + One { + Self::from_diagonal_element_generic($($gargs, )* elt) + } + } + + impl Matrix + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + + /// Creates a matrix filled with random values. + #[inline] + pub fn new_random($($args: usize),*) -> Matrix { + Self::new_random_generic($($gargs),*) + } + } + } +); + +// FIXME: this is not very pretty. We could find a better call syntax. +impl_constructors!(R, C; // Arguments for Matrix + => R: DimName, => C: DimName; // Type parameters for impl + R::name(), C::name(); // Arguments for `_generic` constructors. + ); // Arguments for non-generic constructors. + +impl_constructors!(R, Dynamic; + => R: DimName; + R::name(), Dynamic::new(ncols); + ncols); + +impl_constructors!(Dynamic, C; + => C: DimName; + Dynamic::new(nrows), C::name(); + nrows); + +impl_constructors!(Dynamic, Dynamic; + ; + Dynamic::new(nrows), Dynamic::new(ncols); + nrows, ncols); + +/* + * + * Zero, One, Rand traits. + * + */ +impl Zero for Matrix + where N: Scalar + Zero + ClosedAdd, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn zero() -> Self { + Self::from_element(N::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.iter().all(|e| e.is_zero()) + } +} + +impl One for Matrix + where N: Scalar + Zero + One + ClosedMul + ClosedAdd, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Bounded for Matrix + where N: Scalar + Bounded, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn max_value() -> Self { + Self::from_element(N::max_value()) + } + + #[inline] + fn min_value() -> Self { + Self::from_element(N::min_value()) + } +} + +impl Rand for Matrix + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut G) -> Self { + let nrows = R::try_to_usize().unwrap_or(rng.gen_range(0, 10)); + let ncols = C::try_to_usize().unwrap_or(rng.gen_range(0, 10)); + + Self::from_fn_generic(R::from_usize(nrows), C::from_usize(ncols), |_, _| rng.gen()) + } +} + + +#[cfg(feature = "arbitrary")] +impl Arbitrary for Matrix + where R: Dim, C: Dim, + N: Scalar + Arbitrary + Send, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(g: &mut G) -> Self { + let nrows = R::try_to_usize().unwrap_or(g.gen_range(0, 10)); + let ncols = C::try_to_usize().unwrap_or(g.gen_range(0, 10)); + + Self::from_fn_generic(R::from_usize(nrows), C::from_usize(ncols), |_, _| N::arbitrary(g)) + } +} + + +/* + * + * Constructors for small matrices and vectors. + * + */ +macro_rules! componentwise_constructors_impl( + ($($R: ty, $C: ty, $($args: ident:($irow: expr,$icol: expr)),*);* $(;)*) => {$( + impl Matrix + where N: Scalar, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Initializes this matrix from its components. + #[inline] + pub fn new($($args: N),*) -> Matrix { + unsafe { + let mut res = Self::new_uninitialized(); + $( *res.get_unchecked_mut($irow, $icol) = $args; )* + + res + } + } + } + )*} +); + +componentwise_constructors_impl!( + /* + * Square matrices 1 .. 6. + */ + U2, U2, m11:(0,0), m12:(0,1), + m21:(1,0), m22:(1,1); + U3, U3, m11:(0,0), m12:(0,1), m13:(0,2), + m21:(1,0), m22:(1,1), m23:(1,2), + m31:(2,0), m32:(2,1), m33:(2,2); + U4, U4, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3); + U5, U5, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), m45:(3,4), + m51:(4,0), m52:(4,1), m53:(4,2), m54:(4,3), m55:(4,4); + U6, U6, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), m16:(0,5), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), m26:(1,5), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), m36:(2,5), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), m45:(3,4), m46:(3,5), + m51:(4,0), m52:(4,1), m53:(4,2), m54:(4,3), m55:(4,4), m56:(4,5), + m61:(5,0), m62:(5,1), m63:(5,2), m64:(5,3), m65:(5,4), m66:(5,5); + + /* + * Rectangular matrices with 2 rows. + */ + U2, U3, m11:(0,0), m12:(0,1), m13:(0,2), + m21:(1,0), m22:(1,1), m23:(1,2); + U2, U4, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3); + U2, U5, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4); + U2, U6, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), m16:(0,5), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), m26:(1,5); + + /* + * Rectangular matrices with 3 rows. + */ + U3, U2, m11:(0,0), m12:(0,1), + m21:(1,0), m22:(1,1), + m31:(2,0), m32:(2,1); + U3, U4, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3); + U3, U5, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4); + U3, U6, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), m16:(0,5), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), m26:(1,5), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), m36:(2,5); + + /* + * Rectangular matrices with 4 rows. + */ + U4, U2, m11:(0,0), m12:(0,1), + m21:(1,0), m22:(1,1), + m31:(2,0), m32:(2,1), + m41:(3,0), m42:(3,1); + U4, U3, m11:(0,0), m12:(0,1), m13:(0,2), + m21:(1,0), m22:(1,1), m23:(1,2), + m31:(2,0), m32:(2,1), m33:(2,2), + m41:(3,0), m42:(3,1), m43:(3,2); + U4, U5, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), m45:(3,4); + U4, U6, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), m16:(0,5), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), m26:(1,5), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), m36:(2,5), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), m45:(3,4), m46:(3,5); + + /* + * Rectangular matrices with 5 rows. + */ + U5, U2, m11:(0,0), m12:(0,1), + m21:(1,0), m22:(1,1), + m31:(2,0), m32:(2,1), + m41:(3,0), m42:(3,1), + m51:(4,0), m52:(4,1); + U5, U3, m11:(0,0), m12:(0,1), m13:(0,2), + m21:(1,0), m22:(1,1), m23:(1,2), + m31:(2,0), m32:(2,1), m33:(2,2), + m41:(3,0), m42:(3,1), m43:(3,2), + m51:(4,0), m52:(4,1), m53:(4,2); + U5, U4, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), + m51:(4,0), m52:(4,1), m53:(4,2), m54:(4,3); + U5, U6, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), m16:(0,5), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), m26:(1,5), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), m36:(2,5), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), m45:(3,4), m46:(3,5), + m51:(4,0), m52:(4,1), m53:(4,2), m54:(4,3), m55:(4,4), m56:(4,5); + + /* + * Rectangular matrices with 6 rows. + */ + U6, U2, m11:(0,0), m12:(0,1), + m21:(1,0), m22:(1,1), + m31:(2,0), m32:(2,1), + m41:(3,0), m42:(3,1), + m51:(4,0), m52:(4,1), + m61:(5,0), m62:(5,1); + U6, U3, m11:(0,0), m12:(0,1), m13:(0,2), + m21:(1,0), m22:(1,1), m23:(1,2), + m31:(2,0), m32:(2,1), m33:(2,2), + m41:(3,0), m42:(3,1), m43:(3,2), + m51:(4,0), m52:(4,1), m53:(4,2), + m61:(5,0), m62:(5,1), m63:(5,2); + U6, U4, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), + m51:(4,0), m52:(4,1), m53:(4,2), m54:(4,3), + m61:(5,0), m62:(5,1), m63:(5,2), m64:(5,3); + U6, U5, m11:(0,0), m12:(0,1), m13:(0,2), m14:(0,3), m15:(0,4), + m21:(1,0), m22:(1,1), m23:(1,2), m24:(1,3), m25:(1,4), + m31:(2,0), m32:(2,1), m33:(2,2), m34:(2,3), m35:(2,4), + m41:(3,0), m42:(3,1), m43:(3,2), m44:(3,3), m45:(3,4), + m51:(4,0), m52:(4,1), m53:(4,2), m54:(4,3), m55:(4,4), + m61:(5,0), m62:(5,1), m63:(5,2), m64:(5,3), m65:(5,4); + + /* + * Row vectors 1 .. 6. + */ + U1, U1, x:(0,0); + U1, U2, x:(0,0), y:(0,1); + U1, U3, x:(0,0), y:(0,1), z:(0,2); + U1, U4, x:(0,0), y:(0,1), z:(0,2), w:(0,3); + U1, U5, x:(0,0), y:(0,1), z:(0,2), w:(0,3), a:(0,4); + U1, U6, x:(0,0), y:(0,1), z:(0,2), w:(0,3), a:(0,4), b:(0,5); + + /* + * Column vectors 1 .. 6. + */ + U2, U1, x:(0,0), y:(1,0); + U3, U1, x:(0,0), y:(1,0), z:(2,0); + U4, U1, x:(0,0), y:(1,0), z:(2,0), w:(3,0); + U5, U1, x:(0,0), y:(1,0), z:(2,0), w:(3,0), a:(4,0); + U6, U1, x:(0,0), y:(1,0), z:(2,0), w:(3,0), a:(4,0), b:(5,0); +); + +/* + * + * Axis constructors. + * + */ +impl ColumnVector +where N: Scalar + Zero + One, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// The column vector with a 1 as its first component, and zero elsewhere. + #[inline] + pub fn x() -> Self + where R::Value: Cmp { + let mut res = Self::from_element(N::zero()); + unsafe { *res.get_unchecked_mut(0, 0) = N::one(); } + + res + } + + /// The column vector with a 1 as its second component, and zero elsewhere. + #[inline] + pub fn y() -> Self + where R::Value: Cmp { + let mut res = Self::from_element(N::zero()); + unsafe { *res.get_unchecked_mut(1, 0) = N::one(); } + + res + } + + /// The column vector with a 1 as its third component, and zero elsewhere. + #[inline] + pub fn z() -> Self + where R::Value: Cmp { + let mut res = Self::from_element(N::zero()); + unsafe { *res.get_unchecked_mut(2, 0) = N::one(); } + + res + } + + /// The column vector with a 1 as its fourth component, and zero elsewhere. + #[inline] + pub fn w() -> Self + where R::Value: Cmp { + let mut res = Self::from_element(N::zero()); + unsafe { *res.get_unchecked_mut(3, 0) = N::one(); } + + res + } + + /// The column vector with a 1 as its fifth component, and zero elsewhere. + #[inline] + pub fn a() -> Self + where R::Value: Cmp { + let mut res = Self::from_element(N::zero()); + unsafe { *res.get_unchecked_mut(4, 0) = N::one(); } + + res + } + + /// The column vector with a 1 as its sixth component, and zero elsewhere. + #[inline] + pub fn b() -> Self + where R::Value: Cmp { + let mut res = Self::from_element(N::zero()); + unsafe { *res.get_unchecked_mut(5, 0) = N::one(); } + + res + } + + /// The unit column vector with a 1 as its first component, and zero elsewhere. + #[inline] + pub fn x_axis() -> Unit + where R::Value: Cmp { + Unit::new_unchecked(Self::x()) + } + + /// The unit column vector with a 1 as its second component, and zero elsewhere. + #[inline] + pub fn y_axis() -> Unit + where R::Value: Cmp { + Unit::new_unchecked(Self::y()) + } + + /// The unit column vector with a 1 as its third component, and zero elsewhere. + #[inline] + pub fn z_axis() -> Unit + where R::Value: Cmp { + Unit::new_unchecked(Self::z()) + } + + /// The unit column vector with a 1 as its fourth component, and zero elsewhere. + #[inline] + pub fn w_axis() -> Unit + where R::Value: Cmp { + Unit::new_unchecked(Self::w()) + } + + /// The unit column vector with a 1 as its fifth component, and zero elsewhere. + #[inline] + pub fn a_axis() -> Unit + where R::Value: Cmp { + Unit::new_unchecked(Self::a()) + } + + /// The unit column vector with a 1 as its sixth component, and zero elsewhere. + #[inline] + pub fn b_axis() -> Unit + where R::Value: Cmp { + Unit::new_unchecked(Self::b()) + } +} diff --git a/src/core/conversion.rs b/src/core/conversion.rs new file mode 100644 index 00000000..8c99d6c5 --- /dev/null +++ b/src/core/conversion.rs @@ -0,0 +1,59 @@ +use alga::general::{SubsetOf, SupersetOf}; + +use core::{Scalar, Matrix}; +use core::dimension::Dim; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; +use core::storage::OwnedStorage; +use core::allocator::{OwnedAllocator, SameShapeAllocator}; + + +// FIXME: too bad this won't work allo slice conversions. +impl SubsetOf> for Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N1: Scalar, + N2: Scalar + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SB::Alloc: OwnedAllocator, + SA::Alloc: OwnedAllocator + + SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + #[inline] + fn to_superset(&self) -> Matrix { + let (nrows, ncols) = self.shape(); + let nrows2 = R2::from_usize(nrows); + let ncols2 = C2::from_usize(ncols); + + let mut res = unsafe { Matrix::::new_uninitialized_generic(nrows2, ncols2) }; + for i in 0 .. nrows { + for j in 0 .. ncols { + unsafe { + *res.get_unchecked_mut(i, j) = N2::from_subset(self.get_unchecked(i, j)) + } + } + } + + res + } + + #[inline] + fn is_in_subset(m: &Matrix) -> bool { + m.iter().all(|e| e.is_in_subset()) + } + + #[inline] + unsafe fn from_superset_unchecked(m: &Matrix) -> Self { + let (nrows2, ncols2) = m.shape(); + let nrows = R1::from_usize(nrows2); + let ncols = C1::from_usize(ncols2); + + let mut res = Self::new_uninitialized_generic(nrows, ncols); + for i in 0 .. nrows2 { + for j in 0 .. ncols2 { + *res.get_unchecked_mut(i, j) = m.get_unchecked(i, j).to_subset_unchecked() + } + } + + res + } +} diff --git a/src/core/coordinates.rs b/src/core/coordinates.rs new file mode 100644 index 00000000..6fff9193 --- /dev/null +++ b/src/core/coordinates.rs @@ -0,0 +1,230 @@ +use std::mem; +use std::ops::{Deref, DerefMut}; + +use core::{Scalar, Matrix}; +use core::dimension::{U1, U2, U3, U4, U5, U6}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +/* + * + * Give coordinates to owned Vector{1 .. 6} and Matrix{1 .. 6} + * + */ + +macro_rules! coords_impl( + ($T: ident; $($comps: ident),*) => { + #[repr(C)] + #[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] + pub struct $T { + $(pub $comps: N),* + } + } +); + + +macro_rules! deref_impl( + ($R: ty, $C: ty; $Target: ident) => { + impl Deref for Matrix + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Target = $Target; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { mem::transmute(self) } + } + } + + impl DerefMut for Matrix + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { mem::transmute(self) } + } + } + } +); + +/* + * + * Vector coordinates. + * + */ +coords_impl!(X; x); +coords_impl!(XY; x, y); +coords_impl!(XYZ; x, y, z); +coords_impl!(XYZW; x, y, z, w); +coords_impl!(XYZWA; x, y, z, w, a); +coords_impl!(XYZWAB; x, y, z, w, a, b); +coords_impl!(IJKW; i, j, k, w); + +/* + * Rectangular matrices with 2 rows. + */ +coords_impl!(M2x2; m11, m21, + m12, m22); +coords_impl!(M2x3; m11, m21, + m12, m22, + m13, m23); +coords_impl!(M2x4; m11, m21, + m12, m22, + m13, m23, + m14, m24); +coords_impl!(M2x5; m11, m21, + m12, m22, + m13, m23, + m14, m24, + m15, m25); +coords_impl!(M2x6; m11, m21, + m12, m22, + m13, m23, + m14, m24, + m15, m25, + m16, m26); + +/* + * Rectangular matrices with 3 rows. + */ +coords_impl!(M3x2; m11, m21, m31, + m12, m22, m32); +coords_impl!(M3x3; m11, m21, m31, + m12, m22, m32, + m13, m23, m33); +coords_impl!(M3x4; m11, m21, m31, + m12, m22, m32, + m13, m23, m33, + m14, m24, m34); +coords_impl!(M3x5; m11, m21, m31, + m12, m22, m32, + m13, m23, m33, + m14, m24, m34, + m15, m25, m35); +coords_impl!(M3x6; m11, m21, m31, + m12, m22, m32, + m13, m23, m33, + m14, m24, m34, + m15, m25, m35, + m16, m26, m36); + +/* + * Rectangular matrices with 4 rows. + */ +coords_impl!(M4x2; m11, m21, m31, m41, + m12, m22, m32, m42); +coords_impl!(M4x3; m11, m21, m31, m41, + m12, m22, m32, m42, + m13, m23, m33, m43); +coords_impl!(M4x4; m11, m21, m31, m41, + m12, m22, m32, m42, + m13, m23, m33, m43, + m14, m24, m34, m44); +coords_impl!(M4x5; m11, m21, m31, m41, + m12, m22, m32, m42, + m13, m23, m33, m43, + m14, m24, m34, m44, + m15, m25, m35, m45); +coords_impl!(M4x6; m11, m21, m31, m41, + m12, m22, m32, m42, + m13, m23, m33, m43, + m14, m24, m34, m44, + m15, m25, m35, m45, + m16, m26, m36, m46); + +/* + * Rectangular matrices with 5 rows. + */ +coords_impl!(M5x2; m11, m21, m31, m41, m51, + m12, m22, m32, m42, m52); +coords_impl!(M5x3; m11, m21, m31, m41, m51, + m12, m22, m32, m42, m52, + m13, m23, m33, m43, m53); +coords_impl!(M5x4; m11, m21, m31, m41, m51, + m12, m22, m32, m42, m52, + m13, m23, m33, m43, m53, + m14, m24, m34, m44, m54); +coords_impl!(M5x5; m11, m21, m31, m41, m51, + m12, m22, m32, m42, m52, + m13, m23, m33, m43, m53, + m14, m24, m34, m44, m54, + m15, m25, m35, m45, m55); +coords_impl!(M5x6; m11, m21, m31, m41, m51, + m12, m22, m32, m42, m52, + m13, m23, m33, m43, m53, + m14, m24, m34, m44, m54, + m15, m25, m35, m45, m55, + m16, m26, m36, m46, m56); + +/* + * Rectangular matrices with 6 rows. + */ + +coords_impl!(M6x2; m11, m21, m31, m41, m51, m61, + m12, m22, m32, m42, m52, m62); +coords_impl!(M6x3; m11, m21, m31, m41, m51, m61, + m12, m22, m32, m42, m52, m62, + m13, m23, m33, m43, m53, m63); +coords_impl!(M6x4; m11, m21, m31, m41, m51, m61, + m12, m22, m32, m42, m52, m62, + m13, m23, m33, m43, m53, m63, + m14, m24, m34, m44, m54, m64); +coords_impl!(M6x5; m11, m21, m31, m41, m51, m61, + m12, m22, m32, m42, m52, m62, + m13, m23, m33, m43, m53, m63, + m14, m24, m34, m44, m54, m64, + m15, m25, m35, m45, m55, m65); +coords_impl!(M6x6; m11, m21, m31, m41, m51, m61, + m12, m22, m32, m42, m52, m62, + m13, m23, m33, m43, m53, m63, + m14, m24, m34, m44, m54, m64, + m15, m25, m35, m45, m55, m65, + m16, m26, m36, m46, m56, m66); + +/* + * + * Attach coordinates to matrices. + * + */ +deref_impl!(U1, U1; X); +deref_impl!(U2, U1; XY); +deref_impl!(U3, U1; XYZ); +deref_impl!(U4, U1; XYZW); +deref_impl!(U5, U1; XYZWA); +deref_impl!(U6, U1; XYZWAB); + +deref_impl!(U1, U2; XY); +deref_impl!(U1, U3; XYZ); +deref_impl!(U1, U4; XYZW); +deref_impl!(U1, U5; XYZWA); +deref_impl!(U1, U6; XYZWAB); + +deref_impl!(U2, U2; M2x2); +deref_impl!(U2, U3; M2x3); +deref_impl!(U2, U4; M2x4); +deref_impl!(U2, U5; M2x5); +deref_impl!(U2, U6; M2x6); + +deref_impl!(U3, U2; M3x2); +deref_impl!(U3, U3; M3x3); +deref_impl!(U3, U4; M3x4); +deref_impl!(U3, U5; M3x5); +deref_impl!(U3, U6; M3x6); + +deref_impl!(U4, U2; M4x2); +deref_impl!(U4, U3; M4x3); +deref_impl!(U4, U4; M4x4); +deref_impl!(U4, U5; M4x5); +deref_impl!(U4, U6; M4x6); + +deref_impl!(U5, U2; M5x2); +deref_impl!(U5, U3; M5x3); +deref_impl!(U5, U4; M5x4); +deref_impl!(U5, U5; M5x5); +deref_impl!(U5, U6; M5x6); + +deref_impl!(U6, U2; M6x2); +deref_impl!(U6, U3; M6x3); +deref_impl!(U6, U4; M6x4); +deref_impl!(U6, U5; M6x5); +deref_impl!(U6, U6; M6x6); diff --git a/src/core/decompositions.rs b/src/core/decompositions.rs new file mode 100644 index 00000000..7c19bd21 --- /dev/null +++ b/src/core/decompositions.rs @@ -0,0 +1,373 @@ +use std::cmp; + +use alga::general::Real; +use core::{SquareMatrix, OwnedSquareMatrix, ColumnVector, OwnedColumnVector}; +use dimension::{Dim, Dynamic, U1}; +use storage::{Storage, OwnedStorage}; +use allocator::{Allocator, OwnedAllocator}; + + + +impl SquareMatrix + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Get the householder matrix corresponding to a reflexion to the hyperplane + /// defined by `vector`. It can be a reflexion contained in a subspace. + /// + /// # Arguments + /// * `dimension` - the dimension of the space the resulting matrix operates in + /// * `start` - the starting dimension of the subspace of the reflexion + /// * `vector` - the vector defining the reflection. + pub fn new_householder_generic(dimension: D, start: usize, vector: &ColumnVector) + -> OwnedSquareMatrix + where D2: Dim, + SB: Storage { + let mut qk = Self::identity_generic(dimension, dimension); + let subdim = vector.shape().0; + + let stop = subdim + start; + + assert!(dimension.value() >= stop, "Householder matrix creation: subspace dimension index out of bounds."); + + for j in start .. stop { + for i in start .. stop { + unsafe { + let vv = *vector.get_unchecked(i - start, 0) * *vector.get_unchecked(j - start, 0); + let qkij = *qk.get_unchecked(i, j); + *qk.get_unchecked_mut(i, j) = qkij - vv - vv; + } + } + } + + qk + } +} + + +impl> SquareMatrix { + /// QR decomposition using Householder reflections. + pub fn qr(self) -> (OwnedSquareMatrix, OwnedSquareMatrix) + where S::Alloc: Allocator + + Allocator { + + let (nrows, ncols) = self.data.shape(); + + // XXX: too restrictive. + assert!(nrows.value() >= ncols.value(), ""); + + let mut q = OwnedSquareMatrix::::identity_generic(nrows, ncols); + let mut r = self.into_owned(); + + // Temporary buffer that contains a column. + let mut col = unsafe { + OwnedColumnVector::::new_uninitialized_generic(nrows, U1) + }; + + for ite in 0 .. cmp::min(nrows.value() - 1, ncols.value()) { + let subdim = Dynamic::new(nrows.value() - ite); + let mut v = col.rows_mut(0, subdim.value()); + v.copy_from(&r.generic_slice((ite, ite), (subdim, U1))); + + let alpha = + if unsafe { *v.get_unchecked(ite, 0) } >= ::zero() { + -v.norm() + } + else { + v.norm() + }; + + unsafe { + let x = *v.get_unchecked(0, 0); + *v.get_unchecked_mut(0, 0) = x - alpha; + } + + if !v.normalize_mut().is_zero() { + let mut qk = OwnedSquareMatrix::::new_householder_generic(nrows, ite, &v); + r = &qk * r; + + // FIXME: add a method `q.mul_tr(qk) := q * qk.transpose` ? + qk.transpose_mut(); + q = q * qk; + } + } + + (q, r) + } + + /// Eigendecomposition of a square symmetric matrix. + pub fn eig(&self, eps: N, niter: usize) + -> (OwnedSquareMatrix, OwnedColumnVector) + where S::Alloc: Allocator + + Allocator { + + assert!(self.is_square(), + "Unable to compute the eigenvectors and eigenvalues of a non-square matrix."); + + let dim = self.data.shape().0; + + let (mut eigenvectors, mut eigenvalues) = self.hessenberg(); + + if dim.value() == 1 { + return (eigenvectors, eigenvalues.diagonal()); + } + + // Allocate arrays for Givens rotation components + let mut c = unsafe { OwnedColumnVector::::new_uninitialized_generic(dim, U1) }; + let mut s = unsafe { OwnedColumnVector::::new_uninitialized_generic(dim, U1) }; + + let mut iter = 0; + let mut curdim = dim.value() - 1; + + for _ in 0 .. dim.value() { + + let mut stop = false; + + while !stop && iter < niter { + + let lambda; + + unsafe { + let a = *eigenvalues.get_unchecked(curdim - 1, curdim - 1); + let b = *eigenvalues.get_unchecked(curdim - 1, curdim); + let c = *eigenvalues.get_unchecked(curdim, curdim - 1); + let d = *eigenvalues.get_unchecked(curdim, curdim); + + let trace = a + d; + let determinant = a * d - b * c; + + let constquarter: N = ::convert(0.25f64); + let consthalf: N = ::convert(0.5f64); + + let e = (constquarter * trace * trace - determinant).sqrt(); + + let lambda1 = consthalf * trace + e; + let lambda2 = consthalf * trace - e; + + if (lambda1 - d).abs() < (lambda2 - d).abs() { + lambda = lambda1; + } + else { + lambda = lambda2; + } + + } + + // Shift matrix + for k in 0 .. curdim + 1 { + unsafe { + let a = *eigenvalues.get_unchecked(k, k); + *eigenvalues.get_unchecked_mut(k, k) = a - lambda; + } + } + + + // Givens rotation from left + for k in 0 .. curdim { + let x_i = unsafe { *eigenvalues.get_unchecked(k, k) }; + let x_j = unsafe { *eigenvalues.get_unchecked(k + 1, k) }; + + let ctmp; + let stmp; + + if x_j.abs() < eps { + ctmp = N::one(); + stmp = N::zero(); + } + else if x_i.abs() < eps { + ctmp = N::zero(); + stmp = -N::one(); + } + else { + let r = x_i.hypot(x_j); + ctmp = x_i / r; + stmp = -x_j / r; + } + + c[k] = ctmp; + s[k] = stmp; + + for j in k .. (curdim + 1) { + unsafe { + let a = *eigenvalues.get_unchecked(k, j); + let b = *eigenvalues.get_unchecked(k + 1, j); + + *eigenvalues.get_unchecked_mut(k, j) = ctmp * a - stmp * b; + *eigenvalues.get_unchecked_mut(k + 1, j) = stmp * a + ctmp * b; + } + + } + } + + // Givens rotation from right applied to eigenvalues + for k in 0 .. curdim { + for i in 0 .. (k + 2) { + unsafe { + let a = *eigenvalues.get_unchecked(i, k); + let b = *eigenvalues.get_unchecked(i, k + 1); + + *eigenvalues.get_unchecked_mut(i, k) = c[k] * a - s[k] * b; + *eigenvalues.get_unchecked_mut(i, k + 1) = s[k] * a + c[k] * b; + } + } + } + + + // Shift back + for k in 0 .. curdim + 1 { + unsafe { + let a = *eigenvalues.get_unchecked(k, k); + *eigenvalues.get_unchecked_mut(k, k) = a + lambda; + } + } + + + // Givens rotation from right applied to eigenvectors + for k in 0 .. curdim { + for i in 0 .. dim.value() { + + unsafe { + let a = *eigenvectors.get_unchecked(i, k); + let b = *eigenvectors.get_unchecked(i, k + 1); + + *eigenvectors.get_unchecked_mut(i, k) = c[k] * a - s[k] * b; + *eigenvectors.get_unchecked_mut(i, k + 1) = s[k] * a + c[k] * b; + } + } + } + + iter = iter + 1; + stop = true; + + for j in 0 .. curdim { + // Check last row. + if unsafe { *eigenvalues.get_unchecked(curdim, j) }.abs() >= eps { + stop = false; + break; + } + + // Check last column. + if unsafe { *eigenvalues.get_unchecked(j, curdim) }.abs() >= eps { + stop = false; + break; + } + } + } + + + if stop { + if curdim > 1 { + curdim = curdim - 1; + } + else { + break; + } + } + } + + (eigenvectors, eigenvalues.diagonal()) + } + + /// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T + /// + /// Matrix symmetricness is not checked. Returns `None` if `self` is not definite positive. + #[inline] + pub fn cholesky(&self) -> Option> { + let out = self.transpose(); + self.do_cholesky(out).ok() + } + + /// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T + #[inline] + pub fn cholesky_unchecked(&self) -> Result, &'static str> { + let out = self.transpose(); + + if !out.relative_eq(self, N::default_epsilon(), N::default_max_relative()) { + return Err("Cholesky: Input matrix is not symmetric"); + } + + self.do_cholesky(out) + } + + #[inline(always)] + fn do_cholesky(&self, mut out: OwnedSquareMatrix) + -> Result, &'static str> { + assert!(self.is_square(), "The input matrix must be square."); + + for i in 0 .. out.nrows() { + for j in 0 .. (i + 1) { + + let mut sum = out[(i, j)]; + + for k in 0 .. j { + sum = sum - out[(i, k)] * out[(j, k)]; + } + + if i > j { + out[(i, j)] = sum / out[(j, j)]; + } + else if sum > N::zero() { + out[(i, i)] = sum.sqrt(); + } + else { + return Err("Cholesky: Input matrix is not positive definite to machine precision."); + } + } + } + + for i in 0 .. out.nrows() { + for j in i + 1 .. out.ncols() { + out[(i, j)] = N::zero(); + } + } + + Ok(out) + } + + /// Hessenberg + /// Returns the matrix `self` in Hessenberg form and the corresponding similarity transformation + /// + /// # Returns + /// The tuple (`q`, `h`) that `q * h * q^T = self` + pub fn hessenberg(&self) -> (OwnedSquareMatrix, OwnedSquareMatrix) + where S::Alloc: Allocator + Allocator { + + let (nrows, ncols) = self.data.shape(); + let mut h = self.clone_owned(); + + let mut q = OwnedSquareMatrix::::identity_generic(nrows, ncols); + + if ncols.value() <= 2 { + return (q, h); + } + + // Temporary buffer that contains a column. + let mut col = unsafe { + OwnedColumnVector::::new_uninitialized_generic(nrows, U1) + }; + + for ite in 0 .. (ncols.value() - 2) { + let subdim = Dynamic::new(nrows.value() - (ite + 1)); + let mut v = col.rows_mut(0, subdim.value()); + v.copy_from(&h.generic_slice((ite + 1, ite), (subdim, U1))); + + let alpha = v.norm(); + + unsafe { + let x = *v.get_unchecked(0, 0); + *v.get_unchecked_mut(0, 0) = x - alpha; + } + + if !v.normalize_mut().is_zero() { + // XXX: we output the householder matrix to a pre-allocated matrix instead of + // return a value to `p`. This would avoid allocation at each iteration. + let p = OwnedSquareMatrix::::new_householder_generic(nrows, ite + 1, &v); + + q = q * &p; + h = &p * h * p; + } + } + + (q, h) + } +} diff --git a/src/core/default_allocator.rs b/src/core/default_allocator.rs new file mode 100644 index 00000000..41cdf50d --- /dev/null +++ b/src/core/default_allocator.rs @@ -0,0 +1,104 @@ +use std::mem; +use std::ops::Mul; + +use typenum::Prod; +use generic_array::ArrayLength; + +use core::Scalar; +use core::dimension::{Dim, DimName, Dynamic}; +use core::allocator::Allocator; +use core::matrix_array::MatrixArray; +use core::matrix_vec::MatrixVec; + +/* + * + * Allocator. + * + */ +/// An allocator based on `GenericArray` and `MatrixVec` for statically-sized and dynamically-sized +/// matrices respectively. +pub struct DefaultAllocator; + +// Static - Static +impl Allocator for DefaultAllocator + where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + type Buffer = MatrixArray; + + #[inline] + unsafe fn allocate_uninitialized(_: R, _: C) -> Self::Buffer { + mem::uninitialized() + } + + #[inline] + fn allocate_from_iterator>(nrows: R, ncols: C, iter: I) -> Self::Buffer { + let mut res = unsafe { Self::allocate_uninitialized(nrows, ncols) }; + let mut count = 0; + + for (res, e) in res.iter_mut().zip(iter.into_iter()) { + *res = e; + count += 1; + } + + assert!(count == nrows.value() * ncols.value(), + "Matrix init. from iterator: iterator not long enough."); + + res + } +} + + +// Dynamic - Static +// Dynamic - Dynamic +impl Allocator for DefaultAllocator { + type Buffer = MatrixVec; + + #[inline] + unsafe fn allocate_uninitialized(nrows: Dynamic, ncols: C) -> Self::Buffer { + let mut res = Vec::new(); + let length = nrows.value() * ncols.value(); + res.reserve_exact(length); + res.set_len(length); + + MatrixVec::new(nrows, ncols, res) + } + + #[inline] + fn allocate_from_iterator>(nrows: Dynamic, ncols: C, iter: I) -> Self::Buffer { + let it = iter.into_iter(); + let res: Vec = it.collect(); + assert!(res.len() == nrows.value() * ncols.value(), + "Allocation from iterator error: the iterator did not yield the correct number of elements."); + + MatrixVec::new(nrows, ncols, res) + } +} + + +// Static - Dynamic +impl Allocator for DefaultAllocator { + type Buffer = MatrixVec; + + #[inline] + unsafe fn allocate_uninitialized(nrows: R, ncols: Dynamic) -> Self::Buffer { + let mut res = Vec::new(); + let length = nrows.value() * ncols.value(); + res.reserve_exact(length); + res.set_len(length); + + MatrixVec::new(nrows, ncols, res) + } + + #[inline] + fn allocate_from_iterator>(nrows: R, ncols: Dynamic, iter: I) -> Self::Buffer { + let it = iter.into_iter(); + let res: Vec = it.collect(); + assert!(res.len() == nrows.value() * ncols.value(), + "Allocation from iterator error: the iterator did not yield the correct number of elements."); + + MatrixVec::new(nrows, ncols, res) + } +} diff --git a/src/core/determinant.rs b/src/core/determinant.rs new file mode 100644 index 00000000..8d5bf0cf --- /dev/null +++ b/src/core/determinant.rs @@ -0,0 +1,56 @@ +use num::One; + +use alga::general::{ClosedMul, ClosedSub, ClosedAdd}; + +use core::{Scalar, SquareMatrix}; +use core::dimension::Dim; +use core::storage::Storage; + + +impl SquareMatrix + where N: Scalar + One + ClosedMul + ClosedAdd + ClosedSub, + S: Storage { + /// This matrix determinant. + #[inline] + pub fn determinant(&self) -> N { + assert!(self.is_square(), "Unable to invert a non-square matrix."); + let dim = self.shape().0; + + unsafe { + match dim { + 0 => N::one(), + 1 => { + *self.get_unchecked(0, 0) + }, + 2 => { + let m11 = *self.get_unchecked(0, 0); let m12 = *self.get_unchecked(0, 1); + let m21 = *self.get_unchecked(1, 0); let m22 = *self.get_unchecked(1, 1); + + m11 * m22 - m21 * m12 + }, + 3 => { + let m11 = *self.get_unchecked(0, 0); + let m12 = *self.get_unchecked(0, 1); + let m13 = *self.get_unchecked(0, 2); + + let m21 = *self.get_unchecked(1, 0); + let m22 = *self.get_unchecked(1, 1); + let m23 = *self.get_unchecked(1, 2); + + let m31 = *self.get_unchecked(2, 0); + let m32 = *self.get_unchecked(2, 1); + let m33 = *self.get_unchecked(2, 2); + + let minor_m12_m23 = m22 * m33 - m32 * m23; + let minor_m11_m23 = m21 * m33 - m31 * m23; + let minor_m11_m22 = m21 * m32 - m31 * m22; + + m11 * minor_m12_m23 - m12 * minor_m11_m23 + m13 * minor_m11_m22 + }, + _ => { + unimplemented!() + } + } + } + } +} diff --git a/src/core/dimension.rs b/src/core/dimension.rs new file mode 100644 index 00000000..1f723ad4 --- /dev/null +++ b/src/core/dimension.rs @@ -0,0 +1,342 @@ +use std::fmt::Debug; +use std::any::Any; +use std::ops::{Add, Sub, Mul, Div}; +use typenum::{self, Unsigned, UInt, B1, Bit, UTerm, Sum, Prod, Diff, Quot}; + +/// Dim of dynamically-sized algebraic entities. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub struct Dynamic { + value: usize +} + +impl Dynamic { + /// A dynamic size equal to `value`. + #[inline] + pub fn new(value: usize) -> Dynamic { + Dynamic { + value: value + } + } +} + +/// Trait implemented by dynamic dimensions. +pub trait IsDynamic { } +/// Trait implemented by dimensions that are not equal to U1. +pub trait IsNotStaticOne { } + +impl IsDynamic for Dynamic { } +impl IsNotStaticOne for Dynamic { } + +pub trait Dim: Any + Debug + Copy + PartialEq + Send { + fn try_to_usize() -> Option; + fn value(&self) -> usize; + fn from_usize(dim: usize) -> Self; +} + +impl Dim for Dynamic { + #[inline] + fn try_to_usize() -> Option { + None + } + + #[inline] + fn from_usize(dim: usize) -> Self { + Dynamic::new(dim) + } + + #[inline] + fn value(&self) -> usize { + self.value + } +} + +/* + * + * Operations. + * + */ + +macro_rules! dim_ops( + ($($DimOp: ident, $DimNameOp: ident, + $Op: ident, $op: ident, + $DimResOp: ident, $DimNameResOp: ident, + $ResOp: ident);* $(;)*) => {$( + pub type $DimResOp = >::Output; + + pub trait $DimOp: Dim { + type Output: Dim; + + fn $op(self, other: D) -> Self::Output; + } + + impl $DimOp for D1 + where D1::Value: $Op, + $ResOp: NamedDim { + type Output = <$ResOp as NamedDim>::Name; + + #[inline] + fn $op(self, _: D2) -> Self::Output { + Self::Output::name() + } + } + + impl $DimOp for Dynamic { + type Output = Dynamic; + + #[inline] + fn $op(self, other: D) -> Dynamic { + Dynamic::new(self.value.$op(other.value())) + } + } + + impl $DimOp for D { + type Output = Dynamic; + + #[inline] + fn $op(self, other: Dynamic) -> Dynamic { + Dynamic::new(self.value().$op(other.value)) + } + } + + pub type $DimNameResOp = >::Output; + + pub trait $DimNameOp: DimName { + type Output: DimName; + + fn $op(self, other: D) -> Self::Output; + } + + impl $DimNameOp for D1 + where D1::Value: $Op, + $ResOp: NamedDim { + type Output = <$ResOp as NamedDim>::Name; + + #[inline] + fn $op(self, _: D2) -> Self::Output { + Self::Output::name() + } + } + )*} +); + +dim_ops!( + DimAdd, DimNameAdd, Add, add, DimSum, DimNameSum, Sum; + DimMul, DimNameMul, Mul, mul, DimProd, DimNameProd, Prod; + DimSub, DimNameSub, Sub, sub, DimDiff, DimNameDiff, Diff; + DimDiv, DimNameDiv, Div, div, DimQuot, DimNameQuot, Quot; +); + + +pub trait DimName: Dim { + type Value: NamedDim; + + /// The name of this dimension, i.e., the singleton `Self`. + #[inline] + fn name() -> Self; + + // FIXME: this is not a very idiomatic name. + /// The value of this dimension. + #[inline] + fn dim() -> usize { + Self::Value::to_usize() + } +} + +pub trait NamedDim: Sized + Any + Unsigned { + type Name: DimName; +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct U1; + +impl Dim for U1 { + #[inline] + fn try_to_usize() -> Option { + Some(1) + } + + #[inline] + fn from_usize(dim: usize) -> Self { + assert!(dim == 1, "Mismatched dimension."); + U1 + } + + #[inline] + fn value(&self) -> usize { + 1 + } +} + +impl DimName for U1 { + type Value = typenum::U1; + + #[inline] + fn name() -> Self { + U1 + } +} + +impl NamedDim for typenum::U1{ + type Name = U1; +} + +macro_rules! named_dimension( + ($($D: ident),* $(,)*) => {$( + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + pub struct $D; + + impl Dim for $D { + #[inline] + fn try_to_usize() -> Option { + Some(typenum::$D::to_usize()) + } + + #[inline] + fn from_usize(dim: usize) -> Self { + assert!(dim == typenum::$D::to_usize(), "Mismatched dimension."); + $D + } + + #[inline] + fn value(&self) -> usize { + typenum::$D::to_usize() + } + } + + impl DimName for $D { + type Value = typenum::$D; + + #[inline] + fn name() -> Self { + $D + } + } + + impl NamedDim for typenum::$D { + type Name = $D; + } + + impl IsNotStaticOne for $D { } + )*} +); + + +// We give explicit names to all Unsigned in [0, 128[ +named_dimension!( + U0, /*U1,*/ U2, U3, U4, U5, U6, U7, U8, U9, + U10, U11, U12, U13, U14, U15, U16, U17, U18, U19, + U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, + U30, U31, U32, U33, U34, U35, U36, U37, U38, U39, + U40, U41, U42, U43, U44, U45, U46, U47, U48, U49, + U50, U51, U52, U53, U54, U55, U56, U57, U58, U59, + U60, U61, U62, U63, U64, U65, U66, U67, U68, U69, + U70, U71, U72, U73, U74, U75, U76, U77, U78, U79, + U80, U81, U82, U83, U84, U85, U86, U87, U88, U89, + U90, U91, U92, U93, U94, U95, U96, U97, U98, U99, + U100, U101, U102, U103, U104, U105, U106, U107, U108, U109, + U110, U111, U112, U113, U114, U115, U116, U117, U118, U119, + U120, U121, U122, U123, U124, U125, U126, U127 +); + +// For values greater than U1023, just use the typenum binary representation directly. +impl +NamedDim +for UInt, A>, B>, C>, D>, E>, F>, G> { + type Name = Self; +} + +impl +Dim +for UInt, A>, B>, C>, D>, E>, F>, G> { + #[inline] + fn try_to_usize() -> Option { + Some(Self::to_usize()) + } + + #[inline] + fn from_usize(dim: usize) -> Self { + assert!(dim == Self::to_usize(), "Mismatched dimension."); + Self::new() + } + + #[inline] + fn value(&self) -> usize { + Self::to_usize() + } +} + +impl +DimName +for UInt, A>, B>, C>, D>, E>, F>, G> { + type Value = Self; + + #[inline] + fn name() -> Self { + Self::new() + } +} + +impl +IsNotStaticOne +for UInt, A>, B>, C>, D>, E>, F>, G> { +} + + + +impl NamedDim for UInt { + type Name = UInt; +} + +impl Dim for UInt { + #[inline] + fn try_to_usize() -> Option { + Some(Self::to_usize()) + } + + #[inline] + fn from_usize(dim: usize) -> Self { + assert!(dim == Self::to_usize(), "Mismatched dimension."); + Self::new() + } + + #[inline] + fn value(&self) -> usize { + Self::to_usize() + } +} + +impl DimName for UInt { + type Value = UInt; + + #[inline] + fn name() -> Self { + Self::new() + } +} + +impl IsNotStaticOne for UInt { +} diff --git a/src/core/helper.rs b/src/core/helper.rs new file mode 100644 index 00000000..b45a430d --- /dev/null +++ b/src/core/helper.rs @@ -0,0 +1,19 @@ +#[cfg(feature="arbitrary")] +use quickcheck::{Arbitrary, Gen}; +use rand::{Rand, Rng}; + +/// Simple helper function for rejection sampling +#[cfg(feature="arbitrary")] +#[doc(hidden)] +#[inline] +pub fn reject bool, T: Arbitrary>(g: &mut G, f: F) -> T { + use std::iter; + iter::repeat(()).map(|_| Arbitrary::arbitrary(g)).find(f).unwrap() +} + +#[doc(hidden)] +#[inline] +pub fn reject_rand bool, T: Rand>(g: &mut G, f: F) -> T { + use std::iter; + iter::repeat(()).map(|_| Rand::rand(g)).find(f).unwrap() +} diff --git a/src/core/inverse.rs b/src/core/inverse.rs new file mode 100644 index 00000000..ed066e49 --- /dev/null +++ b/src/core/inverse.rs @@ -0,0 +1,202 @@ +use approx::ApproxEq; + +use alga::general::Field; + +use core::{Scalar, Matrix, SquareMatrix, OwnedSquareMatrix}; +use core::dimension::Dim; +use core::storage::{Storage, StorageMut}; + + +impl SquareMatrix + where N: Scalar + Field + ApproxEq, + S: Storage { + /// Attempts to invert this matrix. + #[inline] + pub fn try_inverse(self) -> Option> { + let mut res = self.into_owned(); + + if res.shape().0 <= 3 { + if res.try_inverse_mut() { + Some(res) + } + else { + None + } + } + else { + gauss_jordan_inverse(res) + } + } +} + + +impl SquareMatrix + where N: Scalar + Field + ApproxEq, + S: StorageMut { + /// Attempts to invert this matrix in-place. Returns `false` and leaves `self` untouched if + /// inversion fails. + #[inline] + pub fn try_inverse_mut(&mut self) -> bool { + assert!(self.is_square(), "Unable to invert a non-square matrix."); + + let dim = self.shape().0; + + unsafe { + match dim { + 0 => true, + 1 => { + if relative_eq!(self.get_unchecked(0, 0), &N::zero()) { + false + } + else { + *self.get_unchecked_mut(0, 0) = N::one() / self.determinant(); + true + } + }, + 2 => { + let determinant = self.determinant(); + + if relative_eq!(&determinant, &N::zero()) { + false + } + else { + let m11 = *self.get_unchecked(0, 0); let m12 = *self.get_unchecked(0, 1); + let m21 = *self.get_unchecked(1, 0); let m22 = *self.get_unchecked(1, 1); + + *self.get_unchecked_mut(0, 0) = m22 / determinant; + *self.get_unchecked_mut(0, 1) = -m12 / determinant; + + *self.get_unchecked_mut(1, 0) = -m21 / determinant; + *self.get_unchecked_mut(1, 1) = m11 / determinant; + + true + } + }, + 3 => { + let m11 = *self.get_unchecked(0, 0); + let m12 = *self.get_unchecked(0, 1); + let m13 = *self.get_unchecked(0, 2); + + let m21 = *self.get_unchecked(1, 0); + let m22 = *self.get_unchecked(1, 1); + let m23 = *self.get_unchecked(1, 2); + + let m31 = *self.get_unchecked(2, 0); + let m32 = *self.get_unchecked(2, 1); + let m33 = *self.get_unchecked(2, 2); + + + let minor_m12_m23 = m22 * m33 - m32 * m23; + let minor_m11_m23 = m21 * m33 - m31 * m23; + let minor_m11_m22 = m21 * m32 - m31 * m22; + + let determinant = m11 * minor_m12_m23 - + m12 * minor_m11_m23 + + m13 * minor_m11_m22; + + if relative_eq!(&determinant, &N::zero()) { + false + } + else { + *self.get_unchecked_mut(0, 0) = minor_m12_m23 / determinant; + *self.get_unchecked_mut(0, 1) = (m13 * m32 - m33 * m12) / determinant; + *self.get_unchecked_mut(0, 2) = (m12 * m23 - m22 * m13) / determinant; + + *self.get_unchecked_mut(1, 0) = -minor_m11_m23 / determinant; + *self.get_unchecked_mut(1, 1) = (m11 * m33 - m31 * m13) / determinant; + *self.get_unchecked_mut(1, 2) = (m13 * m21 - m23 * m11) / determinant; + + *self.get_unchecked_mut(2, 0) = minor_m11_m22 / determinant; + *self.get_unchecked_mut(2, 1) = (m12 * m31 - m32 * m11) / determinant; + *self.get_unchecked_mut(2, 2) = (m11 * m22 - m21 * m12) / determinant; + + true + } + }, + _ => { + let oself = self.clone_owned(); + if let Some(res) = gauss_jordan_inverse(oself) { + self.copy_from(&res); + true + } + else { + false + } + } + } + } + } +} + + +/// Inverts the given matrix using Gauss-Jordan Ellimitation. +fn gauss_jordan_inverse(mut matrix: SquareMatrix) -> Option> + where D: Dim, + N: Scalar + Field + ApproxEq, + S: StorageMut { + + assert!(matrix.is_square(), "Unable to invert a non-square matrix."); + let dim = matrix.data.shape().0; + let mut res: OwnedSquareMatrix = Matrix::identity_generic(dim, dim); + let dim = dim.value(); + + unsafe { + for k in 0 .. dim { + // Search a non-zero value on the k-th column. + // FIXME: would it be worth it to spend some more time searching for the + // max instead? + + let mut n0 = k; // index of a non-zero entry. + + while n0 != dim { + if !matrix.get_unchecked(n0, k).is_zero() { + break; + } + + n0 += 1; + } + + if n0 == dim { + return None + } + + // Swap pivot line. + if n0 != k { + for j in 0 .. dim { + matrix.swap_unchecked((n0, j), (k, j)); + res.swap_unchecked((n0, j), (k, j)); + } + } + + let pivot = *matrix.get_unchecked(k, k); + + for j in k .. dim { + let selfval = *matrix.get_unchecked(k, j) / pivot; + *matrix.get_unchecked_mut(k, j) = selfval; + } + + for j in 0 .. dim { + let resval = *res.get_unchecked(k, j) / pivot; + *res.get_unchecked_mut(k, j) = resval; + } + + for l in 0 .. dim { + if l != k { + let normalizer = *matrix.get_unchecked(l, k); + + for j in k .. dim { + let selfval = *matrix.get_unchecked(l, j) - *matrix.get_unchecked(k, j) * normalizer; + *matrix.get_unchecked_mut(l, j) = selfval; + } + + for j in 0 .. dim { + let resval = *res.get_unchecked(l, j) - *res.get_unchecked(k, j) * normalizer; + *res.get_unchecked_mut(l, j) = resval; + } + } + } + } + + Some(res) + } +} diff --git a/src/core/iter.rs b/src/core/iter.rs new file mode 100644 index 00000000..50af9da5 --- /dev/null +++ b/src/core/iter.rs @@ -0,0 +1,86 @@ +use std::marker::PhantomData; +use std::mem; + +use core::Scalar; +use core::dimension::Dim; +use core::storage::{Storage, StorageMut}; + +macro_rules! iterator { + (struct $Name:ident for $Storage:ident.$ptr: ident -> $Ptr:ty, $Ref:ty, $SRef: ty) => { + + /// An iterator through a dense matrix with arbitrary strides matrix. + pub struct $Name<'a, N: Scalar, R: Dim, C: Dim, S: 'a + $Storage> { + ptr: $Ptr, + inner_ptr: $Ptr, + inner_end: $Ptr, + size: usize, // We can't use an end pointer here because a stride might be zero. + strides: (S::RStride, S::CStride), + _phantoms: PhantomData<($Ref, R, C, S)> + } + + // FIXME: we need to specialize for the case where the matrix storage is owned (in which + // case the iterator is trivial because it does not have any stride). + impl<'a, N: Scalar, R: Dim, C: Dim, S: 'a + $Storage> $Name<'a, N, R, C, S> { + /// Creates a new iterator for the given matrix storage. + pub fn new(storage: $SRef) -> $Name<'a, N, R, C, S> { + let shape = storage.shape(); + let strides = storage.strides(); + let inner_offset = shape.0.value() * strides.0.value(); + let ptr = storage.$ptr(); + + $Name { + ptr: ptr, + inner_ptr: ptr, + inner_end: unsafe { ptr.offset(inner_offset as isize) }, + size: shape.0.value() * shape.1.value(), + strides: strides, + _phantoms: PhantomData + } + } + } + + impl<'a, N: Scalar, R: Dim, C: Dim, S: 'a + $Storage> Iterator for $Name<'a, N, R, C, S> { + type Item = $Ref; + + #[inline] + fn next(&mut self) -> Option<$Ref> { + unsafe { + if self.size == 0 { + None + } else { + self.size -= 1; + + // Jump to the next outer dimension if needed. + if self.ptr == self.inner_end { + let stride = self.strides.1.value() as isize; + self.inner_end = self.ptr.offset(stride); + self.ptr = self.inner_ptr.offset(stride); + self.inner_ptr = self.ptr; + } + + // Go to the next element. + let old = self.ptr; + + let stride = self.strides.0.value() as isize; + self.ptr = self.ptr.offset(stride); + + Some(mem::transmute(old)) + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.size, Some(self.size)) + } + + #[inline] + fn count(self) -> usize { + self.size_hint().0 + } + } + } +} + +iterator!(struct MatrixIter for Storage.ptr -> *const N, &'a N, &'a S); +iterator!(struct MatrixIterMut for StorageMut.ptr_mut -> *mut N, &'a mut N, &'a mut S); diff --git a/src/core/matrix.rs b/src/core/matrix.rs new file mode 100644 index 00000000..cde94f38 --- /dev/null +++ b/src/core/matrix.rs @@ -0,0 +1,1013 @@ +use num::Zero; + +use std::cmp::Ordering; +use std::marker::PhantomData; +use std::fmt; +use std::any::TypeId; +use std::mem; +use approx::ApproxEq; + +use alga::general::{Field, Real}; + +use core::Scalar; +use core::dimension::{Dim, DimAdd, DimSum, U1, U2}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; +use core::iter::{MatrixIter, MatrixIterMut}; +use core::allocator::{Allocator, OwnedAllocator, SameShapeAllocator, SameShapeR, SameShapeC}; +use core::storage::{Storage, StorageMut, Owned, OwnedStorage, MulStorage, TrMulStorage, SumStorage}; + +/// The type of the result of a matrix allocation by the allocator `A`. +pub type OwnedMatrix = Matrix>::Buffer>; + +/// A square matrix. +pub type SquareMatrix = Matrix; + +/// The type of the result of a square matrix allocation by the allocator `A`. +pub type OwnedSquareMatrix = OwnedMatrix; + +/// A matrix with one column and `D` rows. +pub type ColumnVector = Matrix; + +/// An owned matrix with one column and `D` rows. +pub type OwnedColumnVector = OwnedMatrix; + +/// An owned matrix with one row and `D` columns. +pub type OwnedRowVector = OwnedMatrix; + +/// The type of the result of a matrix sum. +pub type MatrixSum = + Matrix, SameShapeC, SumStorage>; + +/// The type of the result of a matrix sum. +pub type ColumnVectorSum = + Matrix, U1, SumStorage>; + +/// The type of the result of a matrix cross product. +pub type MatrixCross = MatrixSum; + +/// The type of the result of a matrix multiplication. +pub type MatrixMul = Matrix>; + +/// The type of the result of a matrix transpose-multiplication. +pub type MatrixTrMul = Matrix>; + +/// The matrix with storage `S` and scalar type changed from `NOld` to `NNew`. +pub type MatrixWithScalar = +Matrix>::Alloc as Allocator>::Buffer>; + +#[repr(C)] +#[derive(RustcEncodable, RustcDecodable, Hash, Debug, Clone, Copy)] +pub struct Matrix { + pub data: S, + _phantoms: PhantomData<(N, R, C)> +} + +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, + _phantoms: PhantomData + } + } +} + +impl> Matrix { + /// Creates a new matrix with the given data. + #[inline] + pub fn from_data(data: S) -> Matrix { + unsafe { + Self::from_data_statically_unchecked(data) + } + } + + /// Moves this matrix into one that owns its data. + #[inline] + pub fn into_owned(self) -> OwnedMatrix { + Matrix::from_data(self.data.into_owned()) + } + + // FIXME: this could probably benefit from specialization. + // XXX: bad name. + /// Moves this matrix into one that owns its data. The actual type of the result depends on + /// matrix storage combination rules for addition. + #[inline] + pub fn into_owned_sum(self) -> MatrixSum + where R2: Dim, C2: Dim, + S::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + if TypeId::of::>() == TypeId::of::>() { + // We can just return `self.into_owned()`. + + unsafe { + // FIXME: check that those copies are optimized away by the compiler. + let owned = self.into_owned(); + let res = mem::transmute_copy(&owned); + mem::forget(owned); + res + } + } + else { + self.clone_owned_sum() + } + } + + /// Clones this matrix into one that owns its data. + #[inline] + pub fn clone_owned(&self) -> OwnedMatrix { + Matrix::from_data(self.data.clone_owned()) + } + + /// Clones this matrix into one that owns its data. The actual type of the result depends on + /// matrix storage combination rules for addition. + #[inline] + pub fn clone_owned_sum(&self) -> MatrixSum + where R2: Dim, C2: Dim, + S::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + let (nrows, ncols) = self.shape(); + let nrows: SameShapeR = Dim::from_usize(nrows); + let ncols: SameShapeC = Dim::from_usize(ncols); + + let mut res: MatrixSum = unsafe { + Matrix::new_uninitialized_generic(nrows, ncols) + }; + + for (r, s) in res.iter_mut().zip(self.iter()) { + *r = *s + } + + res + } + + /// The total number of elements of this matrix. + #[inline] + pub fn len(&self) -> usize { + let (nrows, ncols) = self.shape(); + nrows * ncols + } + + /// The shape (number of rows, number of columns) of this matrix. + #[inline] + pub fn shape(&self) -> (usize, usize) { + let (nrows, ncols) = self.data.shape(); + (nrows.value(), ncols.value()) + } + + /// The number of rows of this matrix. + #[inline] + pub fn nrows(&self) -> usize { + self.shape().0 + } + + /// The number of columns of this matrix. + #[inline] + pub fn ncols(&self) -> usize { + self.shape().1 + } + + /// The strides (row stride, column stride) of this matrix. + #[inline] + pub fn strides(&self) -> (usize, usize) { + let (srows, scols) = self.data.strides(); + (srows.value(), scols.value()) + } + + /// Iterates through this matrix coordinates. + #[inline] + pub fn iter(&self) -> MatrixIter { + MatrixIter::new(&self.data) + } + + /// Computes the row and column coordinates of the i-th element of this matrix seen as a + /// vector. + #[inline] + pub fn vector_to_matrix_index(&self, i: usize) -> (usize, usize) { + let (nrows, ncols) = self.shape(); + + // Two most common uses that should be optimized by the compiler for statically-sized + // matrices. + if nrows == 1 { + (0, i) + } + else if ncols == 1 { + (i, 0) + } + else { + (i % nrows, i / nrows) + } + } + + /// Gets a reference to the element of this matrix at row `irow` and column `icol` without + /// bound-checking. + #[inline] + pub unsafe fn get_unchecked(&self, irow: usize, icol: usize) -> &N { + self.data.get_unchecked(irow, icol) + } + + /// Tests whether `self` and `rhs` are equal up to a given epsilon. + /// + /// See `relative_eq` from the `ApproxEq` trait for more details. + #[inline] + pub fn relative_eq(&self, other: &Matrix, + eps: N::Epsilon, max_relative: N::Epsilon) + -> bool + where N: ApproxEq, + R2: Dim, C2: Dim, + SB: Storage, + N::Epsilon: Copy, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + assert!(self.shape() == other.shape()); + self.iter().zip(other.iter()).all(|(a, b)| a.relative_eq(b, eps, max_relative)) + } +} + +impl> Matrix { + /// Mutably iterates through this matrix coordinates. + #[inline] + pub fn iter_mut(&mut self) -> MatrixIterMut { + MatrixIterMut::new(&mut self.data) + } + + /// Gets a mutable reference to the i-th element of this matrix. + #[inline] + pub unsafe fn get_unchecked_mut(&mut self, irow: usize, icol: usize) -> &mut N { + self.data.get_unchecked_mut(irow, icol) + } + + /// Swaps two entries without bound-checking. + #[inline] + pub unsafe fn swap_unchecked(&mut self, row_cols1: (usize, usize), row_cols2: (usize, usize)) { + self.data.swap_unchecked(row_cols1, row_cols2) + } + + /// Swaps two entries. + #[inline] + pub fn swap(&mut self, row_cols1: (usize, usize), row_cols2: (usize, usize)) { + let (nrows, ncols) = self.shape(); + assert!(row_cols1.0 < nrows && row_cols1.1 < ncols, "Matrix elements swap index out of bounds."); + assert!(row_cols2.0 < nrows && row_cols2.1 < ncols, "Matrix elements swap index out of bounds."); + unsafe { self.swap_unchecked(row_cols1, row_cols2) } + } + + /// Fills this matrix with the content of another one. Both must have the same shape. + #[inline] + pub fn copy_from(&mut self, other: &Matrix) + where R2: Dim, C2: Dim, + SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + assert!(self.shape() == other.shape(), "Unable to copy from a matrix with a different shape."); + + for (out, other) in self.iter_mut().zip(other.iter()) { + *out = *other + } + } + + /// Sets all the entries of this matrix to `value`. + #[inline] + pub fn fill(&mut self, value: N) { + for e in self.iter_mut() { + *e = value + } + } +} + +impl> Matrix + // XXX: see the rust issue #26026 + where S::Alloc: OwnedAllocator { + + /// Extracts a slice containing the entire matrix entries orderd column-by-columns. + #[inline] + pub fn as_slice(&self) -> &[N] { + self.data.as_slice() + } + + /// Extracts a mutable slice containing the entire matrix entries orderd column-by-columns. + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [N] { + self.data.as_mut_slice() + } + + /// Returns a matrix containing the result of `f` applied to each of its entries. + #[inline] + pub fn map N>(&self, mut f: F) -> Matrix { + let shape = self.data.shape(); + + let mut res: Matrix; + res = unsafe { Self::new_uninitialized_generic(shape.0, shape.1) }; + + for i in 0 .. shape.0.value() * shape.1.value() { + unsafe { + let a = *self.data.get_unchecked_linear(i); + *res.data.get_unchecked_linear_mut(i) = f(a) + } + } + + res + } + + /// Returns a matrix containing the result of `f` applied to each entries of `self` and + /// `rhs`. + #[inline] + pub fn zip_map N>(&self, rhs: &Matrix, mut f: F) -> Matrix { + let shape_generic = self.data.shape(); + let shape = self.shape(); + + let mut res: Matrix; + res = unsafe { Self::new_uninitialized_generic(shape_generic.0, shape_generic.1) }; + + assert!(shape == rhs.shape(), "Matrix simultaneous traversal error: dimension mismatch."); + + for i in 0 .. shape.0 * shape.1 { + unsafe { + let a = *self.data.get_unchecked_linear(i); + let b = *rhs.data.get_unchecked_linear(i); + *res.data.get_unchecked_linear_mut(i) = f(a, b) + } + } + + res + } +} + +impl> Matrix + where S::Alloc: Allocator { + /// Transposes `self`. + #[inline] + pub fn transpose(&self) -> OwnedMatrix { + let (nrows, ncols) = self.data.shape(); + + unsafe { + let mut res: OwnedMatrix = Matrix::new_uninitialized_generic(ncols, nrows); + for i in 0 .. nrows.value() { + for j in 0 .. ncols.value() { + *res.get_unchecked_mut(j, i) = *self.get_unchecked(i, j); + } + } + + res + } + } +} + +impl> Matrix { + /// Transposes the square matrix `self` in-place. + pub fn transpose_mut(&mut self) { + assert!(self.is_square(), "Unable to transpose a non-square matrix in-place."); + + let dim = self.shape().0; + + for i in 1 .. dim { + for j in 0 .. i { + unsafe { self.swap_unchecked((i, j), (j, i)) } + } + } + } +} + +impl SquareMatrix + where N: Scalar, + S: Storage, + S::Alloc: Allocator { + /// Creates a square matrix with its diagonal set to `diag` and all other entries set to 0. + #[inline] + pub fn diagonal(&self) -> OwnedColumnVector { + assert!(self.is_square(), "Unable to transpose a non-square matrix in-place."); + + let dim = self.data.shape().0; + let mut res = unsafe { OwnedColumnVector::::new_uninitialized_generic(dim, U1) }; + + for i in 0 .. dim.value() { + unsafe { *res.get_unchecked_mut(i, 0) = *self.get_unchecked(i, i); } + } + + res + } +} + +impl ColumnVector + where N: Scalar + Zero, + D: DimAdd, + S: Storage, + S::Alloc: Allocator, U1> { + /// Computes the coordinates in projective space of this vector, i.e., appends a `0` to its + /// coordinates. + #[inline] + pub fn to_homogeneous(&self) -> OwnedColumnVector, S::Alloc> { + let len = self.len(); + let hnrows = DimSum::::from_usize(len + 1); + let mut res = unsafe { OwnedColumnVector::::new_uninitialized_generic(hnrows, U1) }; + res.generic_slice_mut((0, 0), self.data.shape()).copy_from(self); + res[(len, 0)] = N::zero(); + + res + } +} + + +// // /* +// // * +// // * Conversions (AsRef, AsMut, From) +// // * +// // */ +// // impl FromIterator for Matrix +// // where N: Scalar + Rand, +// // R: Dim, +// // C: Dim, +// // A: Allocator { +// // #[inline] +// // fn from_iter>(iter: I) -> Matrix { +// // let mut iter = iter.into_iter(); +// // } +// // } +// // +// // impl AsRef<[[N; $dimension]; $dimension]> for $t { +// // #[inline] +// // fn as_ref(&self) -> &[[N; $dimension]; $dimension] { +// // unsafe { +// // mem::transmute(self) +// // } +// // } +// // } +// +// // impl AsMut<[[N; $dimension]; $dimension]> for $t { +// // #[inline] +// // fn as_mut(&mut self) -> &mut [[N; $dimension]; $dimension] { +// // unsafe { +// // mem::transmute(self) +// // } +// // } +// // } +// +// // impl<'a, N> From<&'a [[N; $dimension]; $dimension]> for &'a $t { +// // #[inline] +// // fn from(arr: &'a [[N; $dimension]; $dimension]) -> &'a $t { +// // unsafe { +// // mem::transmute(arr) +// // } +// // } +// // } +// +// // impl<'a, N> From<&'a mut [[N; $dimension]; $dimension]> for &'a mut $t { +// // #[inline] +// // fn from(arr: &'a mut [[N; $dimension]; $dimension]) -> &'a mut $t { +// // unsafe { +// // mem::transmute(arr) +// // } +// // } +// // } +// +// // impl<'a, N: Clone> From<&'a [[N; $dimension]; $dimension]> for $t { +// // #[inline] +// // fn from(arr: &'a [[N; $dimension]; $dimension]) -> $t { +// // let tref: &$t = From::from(arr); +// // tref.clone() +// // } +// // } +// +// // impl MatrixEdit for $t { +// // type RowSlice = $dvector; +// // type ColumnSlice = $dvector; +// // type MinorMatrix = $tsmaller; +// // +// // #[inline] +// // fn column_slice(&self, cid: usize, rstart: usize, rend: usize) -> Self::ColumnSlice { +// // let column = self.column(cid); +// // +// // $dvector::from_slice(rend - rstart, &column.as_ref()[rstart .. rend]) +// // } +// +// // #[inline] +// // fn row_slice(&self, rid: usize, cstart: usize, cend: usize) -> Self::RowSlice { +// // let row = self.row(rid); +// // +// // $dvector::from_slice(cend - cstart, &row.as_ref()[cstart .. cend]) +// // } +// +// // // FIXME: optimize that (+ this is a Copy/paste from dmatrix). +// // #[inline] +// // fn delete_row_column(&self, row_id: usize, column_id: usize) -> Self::MinorMatrix { +// // assert!(row_id < $dimension && column_id < $dimension); +// // +// // unsafe { +// // let mut res = $tsmaller::new_uninitialized_generic($dimension - 1, $dimension - 1); +// // +// // for irow in 0 .. row_id { +// // for icol in 0 .. column_id { +// // res.unsafe_set((irow, icol), self.unsafe_at((irow, icol))) +// // } +// // +// // for icol in column_id + 1 .. $dimension { +// // res.unsafe_set((irow, icol - 1), self.unsafe_at((irow, icol))) +// // } +// // } +// // +// // for irow in row_id + 1 .. $dimension { +// // for icol in 0 .. column_id { +// // res.unsafe_set((irow - 1, icol), self.unsafe_at((irow, icol))) +// // } +// // +// // for icol in column_id + 1 .. $dimension { +// // res.unsafe_set((irow - 1, icol - 1), self.unsafe_at((irow, icol))) +// // } +// // } +// // +// // res +// // } +// // } +// +// // // FIXME: optimize that (+ this is a Copy/paste from dmatrix). +// // #[inline] +// // fn swap_rows(&mut self, row_id1: usize, row_id2: usize) { +// // if row_id1 != row_id2 { +// // assert!(row_id1 < $dimension && row_id2 < $dimension); +// // +// // for icol in 0 .. $dimension { +// // self.swap((row_id1, icol), (row_id2, icol)) +// // } +// // } +// // } +// // +// // // FIXME: optimize that (+ this is a Copy/paste from dmatrix). +// // #[inline] +// // fn swap_columns(&mut self, column_id1: usize, column_id2: usize) { +// // if column_id1 != column_id2 { +// // assert!(column_id1 < $dimension && column_id2 < $dimension); +// // +// // for irow in 0 .. $dimension { +// // self.swap((irow, column_id1), (irow, column_id2)) +// // } +// // } +// // } +// // } +// // +// // /* +// // * +// // * Mean +// // * +// // */ +// // impl> Mean<$vector> for $t { +// // fn mean(&self) -> $vector { +// // let mut res: $vector = ::zero(); +// // let normalizer: N = ::convert(1.0f64 / $dimension as f64); +// // +// // for i in 0 .. $dimension { +// // for j in 0 .. $dimension { +// // unsafe { +// // let acc = res.unsafe_at(j) + self.unsafe_at((i, j)) * normalizer; +// // res.unsafe_set(j, acc); +// // } +// // } +// // } +// // +// // res +// // } +// // } +// // +// // /* +// // * +// // * Componentwise unary operations. +// // * +// // */ +// // componentwise_absolute!($t, $($compN),+); +// // ) +// // ); +// +// +// // FIXME: specialize for row-major/column major +// // +// +// // macro_rules! to_homogeneous_impl( +// // ($t: ident, $t2: ident, $dimension: expr, $dim2: expr) => ( +// // impl ToHomogeneous<$t2> for $t { +// // #[inline] +// // fn to_homogeneous(&self) -> $t2 { +// // let mut res: $t2 = ::one(); +// // +// // for i in 0 .. $dimension { +// // for j in 0 .. $dimension { +// // res[(i, j)] = self[(i, j)] +// // } +// // } +// // +// // res +// // } +// // } +// // ) +// // ); +// +// // macro_rules! from_homogeneous_impl( +// // ($t: ident, $t2: ident, $dimension: expr, $dim2: expr) => ( +// // impl FromHomogeneous<$t2> for $t { +// // #[inline] +// // fn from(m: &$t2) -> $t { +// // let mut res: $t = ::one(); +// // +// // for i in 0 .. $dimension { +// // for j in 0 .. $dimension { +// // res[(i, j)] = m[(i, j)] +// // } +// // } +// // +// // // FIXME: do we have to deal the lost components +// // // (like if the 1 is not a 1… do we have to divide?) +// // +// // res +// // } +// // } +// // ) +// // ); +// +// +// // macro_rules! eigen_qr_impl( +// // ($t: ident, $v: ident) => ( +// // impl EigenQR for $t { +// // fn eigen_qr(&self, eps: N, niter: usize) -> ($t, $v) { +// // linalg::eigen_qr(self, eps, niter) +// // } +// // } +// // ) +// // ); + + +impl ApproxEq for Matrix + where N: Scalar + ApproxEq, + S: Storage, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.relative_eq(other, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + assert!(self.shape() == other.shape()); + self.iter().zip(other.iter()).all(|(a, b)| a.ulps_eq(b, epsilon, max_ulps)) + } +} + +impl PartialOrd for Matrix + where N: Scalar + PartialOrd, + S: Storage { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + assert!(self.shape() == other.shape(), "Matrix comparison error: dimensions mismatch."); + + let first_ord = unsafe { self.data.get_unchecked_linear(0).partial_cmp(other.data.get_unchecked_linear(0)) }; + + if let Some(mut first_ord) = first_ord { + let mut it = self.iter().zip(other.iter()); + it.next(); // Drop the first elements (we already tested it). + + for (left, right) in it { + if let Some(ord) = left.partial_cmp(right) { + match ord { + Ordering::Equal => { /* Does not change anything. */}, + Ordering::Less => { + if first_ord == Ordering::Greater { + return None; + } + first_ord = ord + }, + Ordering::Greater => { + if first_ord == Ordering::Less { + return None; + } + first_ord = ord + }, + } + } + else { + return None + } + } + } + + None + } + + #[inline] + fn lt(&self, right: &Self) -> bool { + assert!(self.shape() == right.shape(), "Matrix comparison error: dimensions mismatch."); + self.iter().zip(right.iter()).all(|(a, b)| a.lt(b)) + } + + #[inline] + fn le(&self, right: &Self) -> bool { + assert!(self.shape() == right.shape(), "Matrix comparison error: dimensions mismatch."); + self.iter().zip(right.iter()).all(|(a, b)| a.le(b)) + } + + #[inline] + fn gt(&self, right: &Self) -> bool { + assert!(self.shape() == right.shape(), "Matrix comparison error: dimensions mismatch."); + self.iter().zip(right.iter()).all(|(a, b)| a.gt(b)) + } + + #[inline] + fn ge(&self, right: &Self) -> bool { + assert!(self.shape() == right.shape(), "Matrix comparison error: dimensions mismatch."); + self.iter().zip(right.iter()).all(|(a, b)| a.ge(b)) + } +} + +impl Eq for Matrix + where N: Scalar + Eq, + S: Storage { } + +impl PartialEq for Matrix + where N: Scalar, + S: Storage { + #[inline] + fn eq(&self, right: &Matrix) -> bool { + assert!(self.shape() == right.shape(), "Matrix equality test dimension mismatch."); + self.iter().zip(right.iter()).all(|(l, r)| l == r) + } +} + + +// FIXME: the bounds are much too restrictive here! This won't even work for, e.g., +// integer-valued matrices... +impl fmt::Display for Matrix + where N: Real + fmt::Display, + S: Storage, + S::Alloc: Allocator { + // XXX: will not always work correctly due to rounding errors. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn integral_length(val: &N) -> usize { + let mut res = 1; + let mut curr: N = ::convert(10.0f64); + + while curr <= *val { + curr = curr * ::convert(10.0f64); + res += 1; + } + + if val.is_sign_negative() { + res + 1 + } + else { + res + } + } + + let (nrows, ncols) = self.data.shape(); + let mut max_decimal_length = 0; + let mut decimal_lengths: MatrixWithScalar = + Matrix::from_element_generic(nrows, ncols, 0); + let (nrows, ncols) = self.shape(); + + for i in 0 .. nrows { + for j in 0 .. ncols { + decimal_lengths[(i, j)] = integral_length(&self[(i, j)]); + max_decimal_length = ::max(max_decimal_length, decimal_lengths[(i, j)]); + } + } + + let precision = f.precision().unwrap_or(3); + let max_number_length = max_decimal_length + precision + 1; + + try!(writeln!(f, " ┌ {:>width$} ┐", "", width = max_number_length * ncols + ncols - 1)); + + for i in 0 .. nrows { + try!(write!(f, " │")); + for j in 0 .. ncols { + let number_length = decimal_lengths[(i, j)] + precision + 1; + let pad = max_number_length - number_length; + try!(write!(f, " {:>thepad$}", "", thepad = pad)); + try!(write!(f, "{:.*}", precision, (*self)[(i, j)])); + } + try!(writeln!(f, " │")); + } + + writeln!(f, " └ {:>width$} ┘", "", width = max_number_length * ncols + ncols - 1) + } +} + + +impl Matrix + where N: Scalar + Field, + S: Storage { + /// The dot product between two matrices (seen as vectors). + #[inline] + pub fn dot(&self, other: &Matrix) -> N + where SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + assert!(self.shape() == other.shape(), "Dot product dimension mismatch."); + self.iter().zip(other.iter()).fold(N::zero(), |acc, (a, b)| acc + *a * *b) + } + + // FIXME: we could specialize this for when we only have vectors in which case we can just use + // `iter().zip(iter())` as for the regular `.dot` method. + /// The dot product between the transpose of `self` and `other`. + #[inline] + pub fn tr_dot(&self, other: &Matrix) -> N + where SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + let (nrows, ncols) = self.shape(); + assert!((ncols, nrows) == other.shape(), "Dot product dimension mismatch."); + + let mut res = N::zero(); + + for i in 0 .. nrows { + for j in 0 .. ncols { + unsafe { + res += *self.get_unchecked(i, j) * *other.get_unchecked(j, i); + } + } + } + + res + } + + + /// The squared L2 norm of this matrix. + #[inline] + pub fn norm_squared(&self) -> N { + self.dot(self) + } + + /// The perpendicular product between two 2D column vectors, i.e. `a.x * b.y - a.y * b.x`. + #[inline] + pub fn perp(&self, b: &Matrix) -> N + where R2: Dim, C2: Dim, + SB: Storage, + ShapeConstraint: SameNumberOfRows + + SameNumberOfColumns + + SameNumberOfRows + + SameNumberOfColumns { + assert!(self.shape() == (2, 1), "2D perpendicular product "); + + unsafe { + *self.get_unchecked(0, 0) * *b.get_unchecked(1, 0) - + *self.get_unchecked(1, 0) * *b.get_unchecked(0, 0) + } + } + + // FIXME: use specialization instead of an assertion. + /// The 3D cross product between two vectors. + /// + /// Panics if the shape is not 3D vector. In the future, this will be implemented only for + /// dynamically-sized matrices and statically-sized 3D matrices. + #[inline] + pub fn cross(&self, b: &Matrix) -> MatrixCross + where R2: Dim, C2: Dim, + SB: Storage, + S::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + let shape = self.shape(); + assert!(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."); + + if shape.0 == 3 { + unsafe { + // FIXME: soooo ugly! + let nrows = SameShapeR::::from_usize(3); + let ncols = SameShapeC::::from_usize(1); + let mut res = Matrix::new_uninitialized_generic(nrows, ncols); + + let ax = *self.get_unchecked(0, 0); + let ay = *self.get_unchecked(1, 0); + let az = *self.get_unchecked(2, 0); + + let bx = *b.get_unchecked(0, 0); + let by = *b.get_unchecked(1, 0); + let bz = *b.get_unchecked(2, 0); + + *res.get_unchecked_mut(0, 0) = ay * bz - az * by; + *res.get_unchecked_mut(1, 0) = az * bx - ax * bz; + *res.get_unchecked_mut(2, 0) = ax * by - ay * bx; + + res + } + } + else { + unsafe { + // FIXME: soooo ugly! + let nrows = SameShapeR::::from_usize(1); + let ncols = SameShapeC::::from_usize(3); + let mut res = Matrix::new_uninitialized_generic(nrows, ncols); + + let ax = *self.get_unchecked(0, 0); + let ay = *self.get_unchecked(0, 1); + let az = *self.get_unchecked(0, 2); + + let bx = *b.get_unchecked(0, 0); + let by = *b.get_unchecked(0, 1); + let bz = *b.get_unchecked(0, 2); + + *res.get_unchecked_mut(0, 0) = ay * bz - az * by; + *res.get_unchecked_mut(0, 1) = az * bx - ax * bz; + *res.get_unchecked_mut(0, 2) = az * bx - ax * bz; + + res + } + } + } +} + +impl Matrix + where N: Real, + S: Storage { + /// The smallest angle between two matrices seen as vectors. + #[inline] + pub fn angle(&self, other: &Matrix) -> N + where SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + let prod = self.dot(other); + let n1 = self.norm(); + let n2 = other.norm(); + + if n1.is_zero() || n2.is_zero() { + N::zero() + } + else { + let cang = prod / (n1 * n2); + + if cang > N::one() { + N::zero() + } + else if cang < -N::one() { + N::pi() + } + else { + cang.acos() + } + } + } + + /// The L2 norm of this matrix. + #[inline] + pub fn norm(&self) -> N { + self.norm_squared().sqrt() + } + + /// Returns a normalized version of this matrix. + #[inline] + pub fn normalize(&self) -> OwnedMatrix { + self / self.norm() + } + + /// Returns a normalized version of this matrix unless its norm as smaller or equal to `eps`. + #[inline] + pub fn try_normalize(&self, min_norm: N) -> Option> { + let n = self.norm(); + + if n <= min_norm { + None + } + else { + Some(self / n) + } + } +} + +impl Matrix + where N: Real, + S: StorageMut { + /// Normalizes this matrix in-place and returns its norm. + #[inline] + pub fn normalize_mut(&mut self) -> N { + let n = self.norm(); + *self /= n; + + n + } + + /// Normalizes this matrix in-place or does nothing if its norm is smaller or equal to `eps`. + /// + /// If the normalization succeded, returns the old normal of this matrix. + #[inline] + pub fn try_normalize_mut(&mut self, min_norm: N) -> Option { + let n = self.norm(); + + if n <= min_norm { + None + } + else { + *self /= n; + Some(n) + } + } +} diff --git a/src/core/matrix_alga.rs b/src/core/matrix_alga.rs new file mode 100644 index 00000000..0c4ae645 --- /dev/null +++ b/src/core/matrix_alga.rs @@ -0,0 +1,788 @@ +use num::{Zero, One}; + +use alga::general::{AbstractMagma, AbstractGroupAbelian, AbstractGroup, AbstractLoop, + AbstractMonoid, AbstractQuasigroup, AbstractSemigroup, AbstractModule, + Module, Field, RingCommutative, Real, Inverse, Additive, Multiplicative, + MeetSemilattice, JoinSemilattice, Lattice, Identity, + ClosedAdd, ClosedNeg, ClosedMul}; +use alga::linear::{VectorSpace, NormedSpace, InnerSpace, FiniteDimVectorSpace, FiniteDimInnerSpace}; + +use core::{Scalar, Matrix, SquareMatrix}; +use core::dimension::{Dim, DimName}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +/* + * + * Additive structures. + * + */ +impl Identity for Matrix + where N: Scalar + Zero, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::from_element(N::zero()) + } +} + +impl AbstractMagma for Matrix + where N: Scalar + ClosedAdd, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, other: &Self) -> Self { + self + other + } +} + +impl Inverse for Matrix + where N: Scalar + ClosedNeg, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Matrix { + -self + } + + #[inline] + fn inverse_mut(&mut self) { + *self = -self.clone() + } +} + +macro_rules! inherit_additive_structure( + ($($marker: ident<$operator: ident> $(+ $bounds: ident)*),* $(,)*) => {$( + impl $marker<$operator> for Matrix + where N: Scalar + $marker<$operator> $(+ $bounds)*, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +inherit_additive_structure!( + AbstractSemigroup + ClosedAdd, + AbstractMonoid + Zero + ClosedAdd, + AbstractQuasigroup + ClosedAdd + ClosedNeg, + AbstractLoop + Zero + ClosedAdd + ClosedNeg, + AbstractGroup + Zero + ClosedAdd + ClosedNeg, + AbstractGroupAbelian + Zero + ClosedAdd + ClosedNeg +); + +impl AbstractModule for Matrix + where N: Scalar + RingCommutative, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type AbstractRing = N; + + #[inline] + fn multiply_by(&self, n: N) -> Self { + self * n + } +} + +impl Module for Matrix + where N: Scalar + RingCommutative, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Ring = N; +} + +impl VectorSpace for Matrix + where N: Scalar + Field, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Field = N; +} + +impl FiniteDimVectorSpace for Matrix + where N: Scalar + Field, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn dimension() -> usize { + R::dim() * C::dim() + } + + #[inline] + fn canonical_basis_element(i: usize) -> Self { + assert!(i < Self::dimension(), "Index out of bound."); + + let mut res = Self::zero(); + unsafe { *res.data.get_unchecked_linear_mut(i) = N::one(); } + + res + } + + #[inline] + fn dot(&self, other: &Self) -> N { + self.dot(other) + } + + #[inline] + unsafe fn component_unchecked(&self, i: usize) -> &N { + self.data.get_unchecked_linear(i) + } + + #[inline] + unsafe fn component_unchecked_mut(&mut self, i: usize) -> &mut N { + self.data.get_unchecked_linear_mut(i) + } +} + +impl NormedSpace for Matrix + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn norm_squared(&self) -> N { + self.norm_squared() + } + + #[inline] + fn norm(&self) -> N { + self.norm() + } + + #[inline] + fn normalize(&self) -> Self { + self.normalize() + } + + #[inline] + fn normalize_mut(&mut self) -> N { + self.normalize_mut() + } + + #[inline] + fn try_normalize(&self, min_norm: N) -> Option { + self.try_normalize(min_norm) + } + + #[inline] + fn try_normalize_mut(&mut self, min_norm: N) -> Option { + self.try_normalize_mut(min_norm) + } +} + +impl InnerSpace for Matrix + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Real = N; + + #[inline] + fn angle(&self, other: &Self) -> N { + self.angle(other) + } + + #[inline] + fn inner_product(&self, other: &Self) -> N { + self.dot(other) + } +} + +// FIXME: specialization will greatly simplify this implementation in the future. +// In particular: +// − use `x()` instead of `::canonical_basis_element` +// − use `::new(x, y, z)` instead of `::from_slice` +impl FiniteDimInnerSpace for Matrix + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn orthonormalize(vs: &mut [Matrix]) -> usize { + let mut nbasis_elements = 0; + + for i in 0 .. vs.len() { + { + let (elt, basis) = vs[.. i + 1].split_last_mut().unwrap(); + + for basis_element in &basis[.. nbasis_elements] { + *elt -= &*basis_element * elt.dot(basis_element) + } + } + + if vs[i].try_normalize_mut(N::zero()).is_some() { + // FIXME: this will be efficient on dynamically-allocated vectors but for + // statically-allocated ones, `.clone_from` would be better. + vs.swap(nbasis_elements, i); + nbasis_elements += 1; + + // All the other vectors will be dependent. + if nbasis_elements == Self::dimension() { + break; + } + } + } + + nbasis_elements + } + + #[inline] + fn orthonormal_subspace_basis(vs: &[Self], mut f: F) + where F: FnMut(&Self) -> bool { + // FIXME: is this necessary? + assert!(vs.len() <= Self::dimension(), "The given set of vectors has no chance of being a free family."); + + match Self::dimension() { + 1 => { + if vs.len() == 0 { + f(&Self::canonical_basis_element(0)); + } + }, + 2 => { + if vs.len() == 0 { + let _ = f(&Self::canonical_basis_element(0)) && + f(&Self::canonical_basis_element(1)); + } + else if vs.len() == 1 { + let v = &vs[0]; + let res = Self::from_column_slice(&[-v[1], v[0]]); + + f(&res.normalize()); + } + + // Otherwise, nothing. + }, + 3 => { + if vs.len() == 0 { + let _ = f(&Self::canonical_basis_element(0)) && + f(&Self::canonical_basis_element(1)) && + f(&Self::canonical_basis_element(2)); + } + else if vs.len() == 1 { + let v = &vs[0]; + let mut a; + + if v[0].abs() > v[1].abs() { + a = Self::from_column_slice(&[v[2], N::zero(), -v[0]]); + } + else { + a = Self::from_column_slice(&[N::zero(), -v[2], v[1]]); + }; + + a.normalize_mut(); + + if f(&a.cross(v)) { + f(&a); + } + } + else if vs.len() == 2 { + f(&vs[0].cross(&vs[1]).normalize()); + } + }, + _ => { + // XXX: use a GenericArray instead. + let mut known_basis = Vec::new(); + + for v in vs.iter() { + known_basis.push(v.normalize()) + } + + for i in 0 .. Self::dimension() - vs.len() { + let mut elt = Self::canonical_basis_element(i); + + for v in &known_basis { + elt -= v * elt.dot(v) + }; + + if let Some(subsp_elt) = elt.try_normalize(N::zero()) { + if !f(&subsp_elt) { return }; + + known_basis.push(subsp_elt); + } + } + } + } + } +} + + +/* + * + * + * Multiplicative structures. + * + * + */ +impl Identity for SquareMatrix + where N: Scalar + Zero + One, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl AbstractMagma for SquareMatrix + where N: Scalar + Zero + ClosedAdd + ClosedMul, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, other: &Self) -> Self { + self * other + } +} + +macro_rules! impl_multiplicative_structure( + ($($marker: ident<$operator: ident> $(+ $bounds: ident)*),* $(,)*) => {$( + impl $marker<$operator> for SquareMatrix + where N: Scalar + Zero + ClosedAdd + ClosedMul + $marker<$operator> $(+ $bounds)*, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +impl_multiplicative_structure!( + AbstractSemigroup, + AbstractMonoid + One +); + +// // FIXME: Field too strong? +// impl Matrix for Matrix +// where N: Scalar + Field, +// S: Storage { +// type Field = N; +// type Row = OwnedMatrix, S::C, S::Alloc>; +// type Column = OwnedMatrix, S::Alloc>; +// type Transpose = OwnedMatrix; + +// #[inline] +// fn nrows(&self) -> usize { +// self.shape().0 +// } + +// #[inline] +// fn ncolumns(&self) -> usize { +// self.shape().1 +// } + +// #[inline] +// fn row(&self, row: usize) -> Self::Row { +// let mut res: Self::Row = ::zero(); + +// for (column, e) in res.iter_mut().enumerate() { +// *e = self[(row, column)]; +// } + +// res +// } + +// #[inline] +// fn column(&self, column: usize) -> Self::Column { +// let mut res: Self::Column = ::zero(); + +// for (row, e) in res.iter_mut().enumerate() { +// *e = self[(row, column)]; +// } + +// res +// } + +// #[inline] +// unsafe fn get_unchecked(&self, i: usize, j: usize) -> Self::Field { +// self.get_unchecked(i, j) +// } + +// #[inline] +// fn transpose(&self) -> Self::Transpose { +// self.transpose() +// } +// } + +// impl MatrixMut for Matrix +// where N: Scalar + Field, +// S: StorageMut { +// #[inline] +// fn set_row_mut(&mut self, irow: usize, row: &Self::Row) { +// assert!(irow < self.shape().0, "Row index out of bounds."); + +// for (icol, e) in row.iter().enumerate() { +// unsafe { self.set_unchecked(irow, icol, *e) } +// } +// } + +// #[inline] +// fn set_column_mut(&mut self, icol: usize, col: &Self::Column) { +// assert!(icol < self.shape().1, "Column index out of bounds."); +// for (irow, e) in col.iter().enumerate() { +// unsafe { self.set_unchecked(irow, icol, *e) } +// } +// } + +// #[inline] +// unsafe fn set_unchecked(&mut self, i: usize, j: usize, val: Self::Field) { +// *self.get_unchecked_mut(i, j) = val +// } +// } + +// // FIXME: Real is needed here only for invertibility... +// impl SquareMatrixMut for $t { +// #[inline] +// fn from_diagonal(diag: &Self::Coordinates) -> Self { +// let mut res: $t = ::zero(); +// res.set_diagonal_mut(diag); +// res +// } + +// #[inline] +// fn set_diagonal_mut(&mut self, diag: &Self::Coordinates) { +// for (i, e) in diag.iter().enumerate() { +// unsafe { self.set_unchecked(i, i, *e) } +// } +// } +// } + + + +// Specializations depending on the dimension. +// matrix_group_approx_impl!(common: $t, 1, $vector, $($compN),+); + +// // FIXME: Real is needed here only for invertibility... +// impl SquareMatrix for $t { +// type Vector = $vector; + +// #[inline] +// fn diagonal(&self) -> Self::Coordinates { +// $vector::new(self.m11) +// } + +// #[inline] +// fn determinant(&self) -> Self::Field { +// self.m11 +// } + +// #[inline] +// fn try_inverse(&self) -> Option { +// let mut res = *self; +// if res.try_inverse_mut() { +// Some(res) +// } +// else { +// None +// } +// } + +// #[inline] +// fn try_inverse_mut(&mut self) -> bool { +// if relative_eq!(&self.m11, &::zero()) { +// false +// } +// else { +// self.m11 = ::one::() / ::determinant(self); + +// true +// } +// } + +// #[inline] +// fn transpose_mut(&mut self) { +// // no-op +// } +// } + +// ident, 2, $vector: ident, $($compN: ident),+) => { +// matrix_group_approx_impl!(common: $t, 2, $vector, $($compN),+); + +// // FIXME: Real is needed only for inversion here. +// impl SquareMatrix for $t { +// type Vector = $vector; + +// #[inline] +// fn diagonal(&self) -> Self::Coordinates { +// $vector::new(self.m11, self.m22) +// } + +// #[inline] +// fn determinant(&self) -> Self::Field { +// self.m11 * self.m22 - self.m21 * self.m12 +// } + +// #[inline] +// fn try_inverse(&self) -> Option { +// let mut res = *self; +// if res.try_inverse_mut() { +// Some(res) +// } +// else { +// None +// } +// } + +// #[inline] +// fn try_inverse_mut(&mut self) -> bool { +// let determinant = ::determinant(self); + +// if relative_eq!(&determinant, &::zero()) { +// false +// } +// else { +// *self = Matrix2::new( +// self.m22 / determinant , -self.m12 / determinant, +// -self.m21 / determinant, self.m11 / determinant); + +// true +// } +// } + +// #[inline] +// fn transpose_mut(&mut self) { +// mem::swap(&mut self.m12, &mut self.m21) +// } +// } + +// ident, 3, $vector: ident, $($compN: ident),+) => { +// matrix_group_approx_impl!(common: $t, 3, $vector, $($compN),+); + +// // FIXME: Real is needed only for inversion here. +// impl SquareMatrix for $t { +// type Vector = $vector; + +// #[inline] +// fn diagonal(&self) -> Self::Coordinates { +// $vector::new(self.m11, self.m22, self.m33) +// } + +// #[inline] +// fn determinant(&self) -> Self::Field { +// let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23; +// let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23; +// let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22; + +// self.m11 * minor_m12_m23 - self.m12 * minor_m11_m23 + self.m13 * minor_m11_m22 +// } + +// #[inline] +// fn try_inverse(&self) -> Option { +// let mut res = *self; +// if res.try_inverse_mut() { +// Some(res) +// } +// else { +// None +// } +// } + +// #[inline] +// fn try_inverse_mut(&mut self) -> bool { +// let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23; +// let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23; +// let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22; + +// let determinant = self.m11 * minor_m12_m23 - +// self.m12 * minor_m11_m23 + +// self.m13 * minor_m11_m22; + +// if relative_eq!(&determinant, &::zero()) { +// false +// } +// else { +// *self = Matrix3::new( +// (minor_m12_m23 / determinant), +// ((self.m13 * self.m32 - self.m33 * self.m12) / determinant), +// ((self.m12 * self.m23 - self.m22 * self.m13) / determinant), + +// (-minor_m11_m23 / determinant), +// ((self.m11 * self.m33 - self.m31 * self.m13) / determinant), +// ((self.m13 * self.m21 - self.m23 * self.m11) / determinant), + +// (minor_m11_m22 / determinant), +// ((self.m12 * self.m31 - self.m32 * self.m11) / determinant), +// ((self.m11 * self.m22 - self.m21 * self.m12) / determinant) +// ); + +// true +// } +// } + +// #[inline] +// fn transpose_mut(&mut self) { +// mem::swap(&mut self.m12, &mut self.m21); +// mem::swap(&mut self.m13, &mut self.m31); +// mem::swap(&mut self.m23, &mut self.m32); +// } +// } + +// ident, $dimension: expr, $vector: ident, $($compN: ident),+) => { +// matrix_group_approx_impl!(common: $t, $dimension, $vector, $($compN),+); + +// // FIXME: Real is needed only for inversion here. +// impl SquareMatrix for $t { +// type Vector = $vector; + +// #[inline] +// fn diagonal(&self) -> Self::Coordinates { +// let mut diagonal: $vector = ::zero(); + +// for i in 0 .. $dimension { +// unsafe { diagonal.unsafe_set(i, self.get_unchecked(i, i)) } +// } + +// diagonal +// } + +// #[inline] +// fn determinant(&self) -> Self::Field { +// // FIXME: extremely naive implementation. +// let mut det = ::zero(); + +// for icol in 0 .. $dimension { +// let e = unsafe { self.unsafe_at((0, icol)) }; + +// if e != ::zero() { +// let minor_mat = self.delete_row_column(0, icol); +// let minor = minor_mat.determinant(); + +// if icol % 2 == 0 { +// det += minor; +// } +// else { +// det -= minor; +// } +// } +// } + +// det +// } + +// #[inline] +// fn try_inverse(&self) -> Option { +// let mut res = *self; +// if res.try_inverse_mut() { +// Some(res) +// } +// else { +// None +// } +// } + +// #[inline] +// fn try_inverse_mut(&mut self) -> bool { +// let mut res: $t = ::one(); + +// // Inversion using Gauss-Jordan elimination +// for k in 0 .. $dimension { +// // search a non-zero value on the k-th column +// // FIXME: would it be worth it to spend some more time searching for the +// // max instead? + +// let mut n0 = k; // index of a non-zero entry + +// while n0 != $dimension { +// if self[(n0, k)] != ::zero() { +// break; +// } + +// n0 = n0 + 1; +// } + +// if n0 == $dimension { +// return false +// } + +// // swap pivot line +// if n0 != k { +// for j in 0 .. $dimension { +// self.swap((n0, j), (k, j)); +// res.swap((n0, j), (k, j)); +// } +// } + +// let pivot = self[(k, k)]; + +// for j in k .. $dimension { +// let selfval = self[(k, j)] / pivot; +// self[(k, j)] = selfval; +// } + +// for j in 0 .. $dimension { +// let resval = res[(k, j)] / pivot; +// res[(k, j)] = resval; +// } + +// for l in 0 .. $dimension { +// if l != k { +// let normalizer = self[(l, k)]; + +// for j in k .. $dimension { +// let selfval = self[(l, j)] - self[(k, j)] * normalizer; +// self[(l, j)] = selfval; +// } + +// for j in 0 .. $dimension { +// let resval = res[(l, j)] - res[(k, j)] * normalizer; +// res[(l, j)] = resval; +// } +// } +// } +// } + +// *self = res; + +// true +// } + +// #[inline] +// fn transpose_mut(&mut self) { +// for i in 1 .. $dimension { +// for j in 0 .. i { +// self.swap((i, j), (j, i)) +// } +// } +// } + + + + +/* + * + * Ordering + * + */ +impl MeetSemilattice for Matrix + where N: Scalar + MeetSemilattice, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn meet(&self, other: &Self) -> Self { + self.zip_map(other, |a, b| a.meet(&b)) + } +} + +impl JoinSemilattice for Matrix + where N: Scalar + JoinSemilattice, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn join(&self, other: &Self) -> Self { + self.zip_map(other, |a, b| a.join(&b)) + } +} + + +impl Lattice for Matrix + where N: Scalar + Lattice, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn meet_join(&self, other: &Self) -> (Self, Self) { + let shape = self.data.shape(); + assert!(shape == other.data.shape(), "Matrix meet/join error: mismatched dimensions."); + + let mut mres = unsafe { Self::new_uninitialized_generic(shape.0, shape.1) }; + let mut jres = unsafe { Self::new_uninitialized_generic(shape.0, shape.1) }; + + for i in 0 .. shape.0.value() * shape.1.value() { + unsafe { + let mj = self.data.get_unchecked_linear(i).meet_join(other.data.get_unchecked_linear(i)); + *mres.data.get_unchecked_linear_mut(i) = mj.0; + *jres.data.get_unchecked_linear_mut(i) = mj.1; + } + } + + (mres, jres) + } +} diff --git a/src/core/matrix_array.rs b/src/core/matrix_array.rs new file mode 100644 index 00000000..6d2e7154 --- /dev/null +++ b/src/core/matrix_array.rs @@ -0,0 +1,187 @@ +use std::ops::{Deref, DerefMut, Mul}; +use std::fmt::{Debug, Formatter, Result}; +use std::hash::{Hash, Hasher}; + +use typenum::Prod; +use generic_array::{ArrayLength, GenericArray}; + +use core::Scalar; +use core::dimension::{DimName, U1}; +use core::storage::{Storage, StorageMut, Owned, OwnedStorage}; +use core::allocator::Allocator; +use core::default_allocator::DefaultAllocator; + + +/* + * + * Static Storage. + * + */ +/// A array-based statically sized matrix data storage. +#[repr(C)] +pub struct MatrixArray +where R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + + data: GenericArray> +} + +impl Hash for MatrixArray +where N: Hash, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + fn hash(&self, state: &mut H) { + self.data[..].hash(state) + } +} + +impl Deref for MatrixArray +where R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + type Target = GenericArray>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for MatrixArray +where R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl Debug for MatrixArray +where N: Debug, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + #[inline] + fn fmt(&self, fmt: &mut Formatter) -> Result { + self.data.fmt(fmt) + } +} + +impl Copy for MatrixArray + where N: Copy, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength, + GenericArray> : Copy +{ } + +impl Clone for MatrixArray + where N: Clone, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + #[inline] + fn clone(&self) -> Self { + MatrixArray { + data: self.data.clone() + } + } +} + +impl Eq for MatrixArray + where N: Eq, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { +} + +impl PartialEq for MatrixArray + where N: PartialEq, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + #[inline] + fn eq(&self, right: &Self) -> bool { + self.data == right.data + } +} + + +unsafe impl Storage for MatrixArray + where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + type RStride = U1; + type CStride = R; + type Alloc = DefaultAllocator; + + #[inline] + fn into_owned(self) -> Owned { + self + } + + #[inline] + fn clone_owned(&self) -> Owned { + let it = self.iter().cloned(); + + Self::Alloc::allocate_from_iterator(self.shape().0, self.shape().1, it) + } + + #[inline] + fn ptr(&self) -> *const N { + self[..].as_ptr() + } + + #[inline] + fn shape(&self) -> (R, C) { + (R::name(), C::name()) + } + + #[inline] + fn strides(&self) -> (Self::RStride, Self::CStride) { + (Self::RStride::name(), Self::CStride::name()) + } +} + +unsafe impl StorageMut for MatrixArray + where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + #[inline] + fn ptr_mut(&mut self) -> *mut N { + self[..].as_mut_ptr() + } +} + +unsafe impl OwnedStorage for MatrixArray + where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + #[inline] + fn as_slice(&self) -> &[N] { + &self[..] + } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [N] { + &mut self[..] + } +} diff --git a/src/core/matrix_slice.rs b/src/core/matrix_slice.rs new file mode 100644 index 00000000..0008c6bc --- /dev/null +++ b/src/core/matrix_slice.rs @@ -0,0 +1,466 @@ +use std::marker::PhantomData; + +use core::{Scalar, Matrix}; +use core::dimension::{Dim, DimName, Dynamic, DimMul, DimProd, U1}; +use core::iter::MatrixIter; +use core::storage::{Storage, StorageMut, Owned}; +use core::allocator::Allocator; + +macro_rules! slice_storage_impl( + ($Storage: ident as $SRef: ty; $T: ident.$get_addr: ident ($Ptr: ty as $Ref: ty)) => { + pub struct $T<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> { + ptr: $Ptr, + shape: (R, C), + strides: (RStride, CStride), + _phantoms: PhantomData<($Ref, Alloc)>, + } + + // Dynamic and () are arbitrary. It's just to be able to call the constructors with + // `Slice::` + impl<'a, N: Scalar, R: Dim, C: Dim> $T<'a, N, R, C, Dynamic, Dynamic, ()> { + /// Create a new matrix slice without bound checking. + #[inline] + pub unsafe fn new_unchecked(storage: $SRef, start: (usize, usize), shape: (R, C)) + -> $T<'a, N, R, C, S::RStride, S::CStride, S::Alloc> + where RStor: Dim, + CStor: Dim, + S: $Storage { + + let strides = storage.strides(); + $T::new_with_strides_unchecked(storage, start, shape, strides) + } + + /// Create a new matrix slice without bound checking. + #[inline] + pub unsafe fn new_with_strides_unchecked(storage: $SRef, + start: (usize, usize), + shape: (R, C), + strides: (RStride, CStride)) + -> $T<'a, N, R, C, RStride, CStride, S::Alloc> + where RStor: Dim, + CStor: Dim, + S: $Storage, + RStride: Dim, + CStride: Dim { + + $T { + ptr: storage.$get_addr(start.0, start.1), + shape: shape, + strides: (strides.0, strides.1), + _phantoms: PhantomData + } + } + } + } +); + +slice_storage_impl!(Storage as &'a S; SliceStorage.get_address_unchecked(*const N as &'a N)); +slice_storage_impl!(StorageMut as &'a mut S; SliceStorageMut.get_address_unchecked_mut(*mut N as &'a mut N)); + + +impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Copy +for SliceStorage<'a, N, R, C, RStride, CStride, Alloc> { } + +impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Clone +for SliceStorage<'a, N, R, C, RStride, CStride, Alloc> { + #[inline] + fn clone(&self) -> Self { + SliceStorage { + ptr: self.ptr, + shape: self.shape, + strides: self.strides, + _phantoms: PhantomData, + } + } +} + +macro_rules! storage_impl( + ($($T: ident),* $(,)*) => {$( + unsafe impl<'a, N, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Storage + for $T<'a, N, R, C, RStride, CStride, Alloc> + where N: Scalar, + Alloc: Allocator { + + type RStride = RStride; + type CStride = CStride; + type Alloc = Alloc; + + #[inline] + fn into_owned(self) -> Owned { + self.clone_owned() + } + + #[inline] + fn clone_owned(&self) -> Owned { + let (nrows, ncols) = self.shape(); + let it = MatrixIter::new(self).cloned(); + Alloc::allocate_from_iterator(nrows, ncols, it) + } + + #[inline] + fn ptr(&self) -> *const N { + self.ptr + } + + #[inline] + fn shape(&self) -> (R, C) { + self.shape + } + + #[inline] + fn strides(&self) -> (Self::RStride, Self::CStride) { + self.strides + } + } + )*} +); + +storage_impl!(SliceStorage, SliceStorageMut); + +unsafe impl<'a, N, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> StorageMut + for SliceStorageMut<'a, N, R, C, RStride, CStride, Alloc> + where N: Scalar, + Alloc: Allocator { + #[inline] + fn ptr_mut(&mut self) -> *mut N { + self.ptr + } +} + + +impl> Matrix { + #[inline] + fn assert_slice_index(&self, start: (usize, usize), shape: (usize, usize), steps: (usize, usize)) { + let my_shape = self.shape(); + assert!(start.0 + (shape.0 - 1) * steps.0 <= my_shape.0, "Matrix slicing out of bounds."); + assert!(start.1 + (shape.1 - 1) * steps.1 <= my_shape.1, "Matrix slicing out of bounds."); + } +} + + +macro_rules! matrix_slice_impl( + ($me: ident: $Me: ty, $MatrixSlice: ident, $SliceStorage: ident, $Storage: ident, $data: expr; + $row: ident, + $rows: ident, + $rows_with_step: ident, + $fixed_rows: ident, + $fixed_rows_with_step: ident, + $rows_generic: ident, + $column: ident, + $columns: ident, + $columns_with_step: ident, + $fixed_columns: ident, + $fixed_columns_with_step: ident, + $columns_generic: ident, + $slice: ident, + $slice_with_steps: ident, + $fixed_slice: ident, + $fixed_slice_with_steps: ident, + $generic_slice: ident, + $generic_slice_with_steps: ident) => { + /// A matrix slice. + pub type $MatrixSlice<'a, N, R, C, RStride, CStride, Alloc> + = Matrix>; + + impl> Matrix { + /* + * + * Row slicing. + * + */ + /// Returns a slice containing the i-th column of this matrix. + #[inline] + pub fn $row($me: $Me, i: usize) -> $MatrixSlice { + $me.$fixed_rows::(i) + } + + /// Extracts from this matrix a set of consecutive rows. + #[inline] + pub fn $rows($me: $Me, first_row: usize, nrows: usize) + -> $MatrixSlice { + + let my_shape = $me.data.shape(); + $me.assert_slice_index((first_row, 0), (nrows, my_shape.1.value()), (1, 1)); + let shape = (Dynamic::new(nrows), my_shape.1); + + unsafe { + let data = $SliceStorage::new_unchecked($data, (first_row, 0), shape); + Matrix::from_data_statically_unchecked(data) + } + } + + /// Extracts from this matrix a set of consecutive rows regularly spaced by `step` rows. + #[inline] + pub fn $rows_with_step($me: $Me, first_row: usize, nrows: usize, step: usize) + -> $MatrixSlice { + + $me.$rows_generic(first_row, Dynamic::new(nrows), Dynamic::new(step)) + } + + /// Extracts a compile-time number of consecutive rows from this matrix. + #[inline] + pub fn $fixed_rows($me: $Me, first_row: usize) + -> $MatrixSlice + where RSlice: DimName { + + let my_shape = $me.data.shape(); + $me.assert_slice_index((first_row, 0), (RSlice::dim(), my_shape.1.value()), (1, 1)); + let shape = (RSlice::name(), my_shape.1); + + unsafe { + let data = $SliceStorage::new_unchecked($data, (first_row, 0), shape); + Matrix::from_data_statically_unchecked(data) + } + } + + /// Extracts from this matrix a compile-time number of rows regularly spaced by `step` rows. + #[inline] + pub fn $fixed_rows_with_step($me: $Me, first_row: usize, step: usize) + -> $MatrixSlice + where RSlice: DimName { + + $me.$rows_generic(first_row, RSlice::name(), Dynamic::new(step)) + } + + /// Extracts from this matrix `nrows` rows regularly spaced by `step` rows. Both argument may + /// or may not be values known at compile-time. + #[inline] + pub fn $rows_generic($me: $Me, row_start: usize, nrows: RSlice, step: RStep) + -> $MatrixSlice, S::CStride, S::Alloc> + where RSlice: Dim, + RStep: DimMul { + + let my_shape = $me.data.shape(); + let my_strides = $me.data.strides(); + $me.assert_slice_index((row_start, 0), (nrows.value(), my_shape.1.value()), (step.value(), 1)); + + let strides = (step.mul(my_strides.0), my_strides.1); + let shape = (nrows, my_shape.1); + + unsafe { + let data = $SliceStorage::new_with_strides_unchecked($data, (row_start, 0), shape, strides); + Matrix::from_data_statically_unchecked(data) + } + } + + /* + * + * Column slicing. + * + */ + /// Returns a slice containing the i-th column of this matrix. + #[inline] + pub fn $column($me: $Me, i: usize) -> $MatrixSlice { + $me.$fixed_columns::(i) + } + + /// Extracts from this matrix a set of consecutive columns. + #[inline] + pub fn $columns($me: $Me, first_col: usize, ncols: usize) + -> $MatrixSlice { + + let my_shape = $me.data.shape(); + $me.assert_slice_index((0, first_col), (my_shape.0.value(), ncols), (1, 1)); + let shape = (my_shape.0, Dynamic::new(ncols)); + + unsafe { + let data = $SliceStorage::new_unchecked($data, (0, first_col), shape); + Matrix::from_data_statically_unchecked(data) + } + } + + /// Extracts from this matrix a set of consecutive columns regularly spaced by `step` columns. + #[inline] + pub fn $columns_with_step($me: $Me, first_col: usize, ncols: usize, step: usize) + -> $MatrixSlice { + + $me.$columns_generic(first_col, Dynamic::new(ncols), Dynamic::new(step)) + } + + /// Extracts a compile-time number of consecutive columns from this matrix. + #[inline] + pub fn $fixed_columns($me: $Me, first_col: usize) + -> $MatrixSlice + where CSlice: DimName { + + let my_shape = $me.data.shape(); + $me.assert_slice_index((0, first_col), (my_shape.0.value(), CSlice::dim()), (1, 1)); + let shape = (my_shape.0, CSlice::name()); + + unsafe { + let data = $SliceStorage::new_unchecked($data, (0, first_col), shape); + Matrix::from_data_statically_unchecked(data) + } + } + + /// Extracts from this matrix a compile-time number of columns regularly spaced by `step` + /// columns. + #[inline] + pub fn $fixed_columns_with_step($me: $Me, first_col: usize, step: usize) + -> $MatrixSlice + where CSlice: DimName { + + $me.$columns_generic(first_col, CSlice::name(), Dynamic::new(step)) + } + + /// Extracts from this matrix `ncols` columns regularly spaced by `step` columns. Both argument may + /// or may not be values known at compile-time. + #[inline] + pub fn $columns_generic($me: $Me, first_col: usize, ncols: CSlice, step: CStep) + -> $MatrixSlice, S::Alloc> + where CSlice: Dim, + CStep: DimMul { + + let my_shape = $me.data.shape(); + let my_strides = $me.data.strides(); + + $me.assert_slice_index((0, first_col), (my_shape.0.value(), ncols.value()), (1, step.value())); + + let strides = (my_strides.0, step.mul(my_strides.1)); + let shape = (my_shape.0, ncols); + + unsafe { + let data = $SliceStorage::new_with_strides_unchecked($data, (0, first_col), shape, strides); + Matrix::from_data_statically_unchecked(data) + } + } + + /* + * + * General slicing. + * + */ + /// Slices this matrix starting at its component `(irow, icol)` and with `(nrows, ncols)` + /// consecutive elements. + #[inline] + pub fn $slice($me: $Me, start: (usize, usize), shape: (usize, usize)) + -> $MatrixSlice { + + $me.assert_slice_index(start, shape, (1, 1)); + let shape = (Dynamic::new(shape.0), Dynamic::new(shape.1)); + + unsafe { + let data = $SliceStorage::new_unchecked($data, start, shape); + Matrix::from_data_statically_unchecked(data) + } + } + + #[inline] + pub fn $slice_with_steps($me: $Me, start: (usize, usize), shape: (usize, usize), steps: (usize, usize)) + -> $MatrixSlice { + let shape = (Dynamic::new(shape.0), Dynamic::new(shape.1)); + let steps = (Dynamic::new(steps.0), Dynamic::new(steps.1)); + + $me.$generic_slice_with_steps(start, shape, steps) + } + + /// Slices this matrix starting at its component `(irow, icol)` and with `(R::dim(), + /// CSlice::dim())` consecutive components. + #[inline] + pub fn $fixed_slice($me: $Me, irow: usize, icol: usize) + -> $MatrixSlice + where RSlice: DimName, + CSlice: DimName { + + $me.assert_slice_index((irow, icol), (RSlice::dim(), CSlice::dim()), (1, 1)); + let shape = (RSlice::name(), CSlice::name()); + + unsafe { + let data = $SliceStorage::new_unchecked($data, (irow, icol), shape); + Matrix::from_data_statically_unchecked(data) + } + } + + #[inline] + pub fn $fixed_slice_with_steps($me: $Me, start: (usize, usize), steps: (usize, usize)) + -> $MatrixSlice + where RSlice: DimName, + CSlice: DimName { + let shape = (RSlice::name(), CSlice::name()); + let steps = (Dynamic::new(steps.0), Dynamic::new(steps.1)); + $me.$generic_slice_with_steps(start, shape, steps) + } + + /// Creates a slice that may or may not have a fixed size and stride. + #[inline] + pub fn $generic_slice($me: $Me, start: (usize, usize), shape: (RSlice, CSlice)) + -> $MatrixSlice + where RSlice: Dim, + CSlice: Dim { + + $me.assert_slice_index(start, (shape.0.value(), shape.1.value()), (1, 1)); + + unsafe { + let data = $SliceStorage::new_unchecked($data, start, shape); + Matrix::from_data_statically_unchecked(data) + } + } + + /// Creates a slice that may or may not have a fixed size and stride. + #[inline] + pub fn $generic_slice_with_steps($me: $Me, + start: (usize, usize), + shape: (RSlice, CSlice), + steps: (RStep, CStep)) + -> $MatrixSlice, DimProd, S::Alloc> + where RSlice: Dim, + CSlice: Dim, + RStep: DimMul, + CStep: DimMul { + + $me.assert_slice_index(start, (shape.0.value(), shape.1.value()), (steps.0.value(), steps.1.value())); + + let my_strides = $me.data.strides(); + let strides = (steps.0.mul(my_strides.0), steps.1.mul(my_strides.1)); + + unsafe { + let data = $SliceStorage::new_with_strides_unchecked($data, start, shape, strides); + Matrix::from_data_statically_unchecked(data) + } + } + } + } +); + +matrix_slice_impl!( + self: &Self, MatrixSlice, SliceStorage, Storage, &self.data; + row, + rows, + rows_with_step, + fixed_rows, + fixed_rows_with_step, + rows_generic, + column, + columns, + columns_with_step, + fixed_columns, + fixed_columns_with_step, + columns_generic, + slice, + slice_with_steps, + fixed_slice, + fixed_slice_with_steps, + generic_slice, + generic_slice_with_steps); + + +matrix_slice_impl!( + self: &mut Self, MatrixSliceMut, SliceStorageMut, StorageMut, &mut self.data; + row_mut, + rows_mut, + rows_with_step_mut, + fixed_rows_mut, + fixed_rows_with_step_mut, + rows_generic_mut, + column_mut, + columns_mut, + columns_with_step_mut, + fixed_columns_mut, + fixed_columns_with_step_mut, + columns_generic_mut, + slice_mut, + slice_with_steps_mut, + fixed_slice_mut, + fixed_slice_with_steps_mut, + generic_slice_mut, + generic_slice_with_steps_mut); diff --git a/src/core/matrix_vec.rs b/src/core/matrix_vec.rs new file mode 100644 index 00000000..2393a15d --- /dev/null +++ b/src/core/matrix_vec.rs @@ -0,0 +1,171 @@ +use std::ops::Deref; + +use core::Scalar; +use core::dimension::{Dim, DimName, Dynamic, U1}; +use core::storage::{Storage, StorageMut, Owned, OwnedStorage}; +use core::default_allocator::DefaultAllocator; + + +/* + * + * Storage. + * + */ +/// A Vec-based matrix data storage. It may be dynamically-sized. +#[repr(C)] +#[derive(Eq, Debug, Clone, PartialEq)] +pub struct MatrixVec { + data: Vec, + nrows: R, + ncols: C +} + +impl MatrixVec { + #[inline] + pub fn new(nrows: R, ncols: C, data: Vec) -> MatrixVec { + MatrixVec { + data: data, + nrows: nrows, + ncols: ncols + } + } + + /// The underlying data storage. + #[inline] + pub fn data(&self) -> &Vec { + &self.data + } + + /// The underlying mutable data storage. + /// + /// This is unsafe because this may cause UB if the vector is modified by the user. + #[inline] + pub unsafe fn data_mut(&mut self) -> &mut Vec { + &mut self.data + } +} + +impl Deref for MatrixVec { + type Target = Vec; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.data + } +} + +/* + * + * Dynamic − Static + * Dynamic − Dynamic + * + */ +unsafe impl Storage for MatrixVec { + type RStride = U1; + type CStride = Dynamic; + type Alloc = DefaultAllocator; + + #[inline] + fn into_owned(self) -> Owned { + self + } + + #[inline] + fn clone_owned(&self) -> Owned { + self.clone() + } + + #[inline] + fn ptr(&self) -> *const N { + self[..].as_ptr() + } + + #[inline] + fn shape(&self) -> (Dynamic, C) { + (self.nrows, self.ncols) + } + + #[inline] + fn strides(&self) -> (Self::RStride, Self::CStride) { + (Self::RStride::name(), self.nrows) + } +} + + +unsafe impl Storage for MatrixVec { + type RStride = U1; + type CStride = R; + type Alloc = DefaultAllocator; + + #[inline] + fn into_owned(self) -> Owned { + self + } + + #[inline] + fn clone_owned(&self) -> Owned { + self.clone() + } + + #[inline] + fn ptr(&self) -> *const N { + self[..].as_ptr() + } + + #[inline] + fn shape(&self) -> (R, Dynamic) { + (self.nrows, self.ncols) + } + + #[inline] + fn strides(&self) -> (Self::RStride, Self::CStride) { + (Self::RStride::name(), self.nrows) + } +} + + + + +/* + * + * StorageMut, OwnedStorage. + * + */ +unsafe impl StorageMut for MatrixVec { + #[inline] + fn ptr_mut(&mut self) -> *mut N { + self.as_mut_slice().as_mut_ptr() + } +} + +unsafe impl OwnedStorage for MatrixVec { + #[inline] + fn as_slice(&self) -> &[N] { + &self[..] + } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [N] { + &mut self.data[..] + } +} + + +unsafe impl StorageMut for MatrixVec { + #[inline] + fn ptr_mut(&mut self) -> *mut N { + self.as_mut_slice().as_mut_ptr() + } +} + +unsafe impl OwnedStorage for MatrixVec { + #[inline] + fn as_slice(&self) -> &[N] { + &self[..] + } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [N] { + &mut self.data[..] + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 00000000..e56db888 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,41 @@ +pub mod dimension; +pub mod constraint; +pub mod allocator; +pub mod storage; +pub mod coordinates; +pub mod ops; +pub mod iter; +pub mod default_allocator; + +mod scalar; +mod matrix; +mod construction; +mod properties; +mod alias; +mod matrix_alga; +mod determinant; +mod inverse; +mod conversion; +mod matrix_slice; +mod matrix_array; +mod matrix_vec; +mod cg; +mod unit; +mod componentwise; + +mod decompositions; + +#[doc(hidden)] +pub mod helper; + +pub use self::scalar::*; +pub use self::matrix::*; +pub use self::unit::*; + +pub use self::dimension::*; +pub use self::default_allocator::*; + +pub use self::alias::*; +pub use self::matrix_slice::*; +pub use self::matrix_array::*; +pub use self::matrix_vec::*; diff --git a/src/core/ops.rs b/src/core/ops.rs new file mode 100644 index 00000000..ebc444cf --- /dev/null +++ b/src/core/ops.rs @@ -0,0 +1,462 @@ +use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Neg, + Index, IndexMut}; +use num::Zero; + +use alga::general::{ClosedMul, ClosedDiv, ClosedAdd, ClosedSub, ClosedNeg}; + +use core::{Scalar, Matrix, OwnedMatrix, MatrixSum, MatrixMul, MatrixTrMul}; +use core::dimension::Dim; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, AreMultipliable}; +use core::storage::{Storage, StorageMut, OwnedStorage}; +use core::allocator::{SameShapeAllocator, Allocator, OwnedAllocator}; + +/* + * + * Indexing. + * + */ +impl> Index for Matrix { + type Output = N; + + #[inline] + fn index(&self, i: usize) -> &N { + let ij = self.vector_to_matrix_index(i); + &self[ij] + } +} + + +impl Index<(usize, usize)> for Matrix + where N: Scalar, + S: Storage { + type Output = N; + + #[inline] + fn index(&self, ij: (usize, usize)) -> &N { + assert!(ij < self.shape(), "Matrix index out of bounds."); + unsafe { self.get_unchecked(ij.0, ij.1) } + } +} + +// Mutable versions. +impl> IndexMut for Matrix { + #[inline] + fn index_mut(&mut self, i: usize) -> &mut N { + let ij = self.vector_to_matrix_index(i); + &mut self[ij] + } +} + +impl IndexMut<(usize, usize)> for Matrix + where N: Scalar, + S: StorageMut { + + #[inline] + fn index_mut(&mut self, ij: (usize, usize)) -> &mut N { + assert!(ij < self.shape(), "Matrix index out of bounds."); + unsafe { self.get_unchecked_mut(ij.0, ij.1) } + } +} + +/* + * + * Neg + * + */ +impl Neg for Matrix + where N: Scalar + ClosedNeg, + S: Storage { + type Output = OwnedMatrix; + + #[inline] + fn neg(self) -> Self::Output { + let mut res = self.into_owned(); + res.neg_mut(); + res + } +} + +impl<'a, N, R: Dim, C: Dim, S> Neg for &'a Matrix + where N: Scalar + ClosedNeg, + S: Storage { + type Output = OwnedMatrix; + + #[inline] + fn neg(self) -> Self::Output { + -self.clone_owned() + } +} + +impl Matrix + where N: Scalar + ClosedNeg, + S: StorageMut { + /// Negates `self` in-place. + #[inline] + pub fn neg_mut(&mut self) { + for e in self.iter_mut() { + *e = -*e + } + } +} + +/* + * + * Addition & Substraction + * + */ +macro_rules! componentwise_binop_impl( + ($Trait: ident, $method: ident, $bound: ident; + $TraitAssign: ident, $method_assign: ident) => { + impl<'b, N, R1, C1, R2, C2, SA, SB> $Trait<&'b Matrix> for Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N: Scalar + $bound, + SA: Storage, + SB: Storage, + SA::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; + + #[inline] + fn $method(self, right: &'b Matrix) -> Self::Output { + assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); + let mut res = self.into_owned_sum::(); + + for (left, right) in res.iter_mut().zip(right.iter()) { + *left = left.$method(*right) + } + + res + } + } + + impl<'a, N, R1, C1, R2, C2, SA, SB> $Trait> for &'a Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N: Scalar + $bound, + SA: Storage, + SB: Storage, + SB::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; + + #[inline] + fn $method(self, right: Matrix) -> Self::Output { + assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); + let mut res = right.into_owned_sum::(); + + for (left, right) in self.iter().zip(res.iter_mut()) { + *right = left.$method(*right) + } + + res + } + } + + impl $Trait> for Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N: Scalar + $bound, + SA: Storage, + SB: Storage, + SA::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; + + #[inline] + fn $method(self, right: Matrix) -> Self::Output { + self.$method(&right) + } + } + + impl<'a, 'b, N, R1, C1, R2, C2, SA, SB> $Trait<&'b Matrix> for &'a Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N: Scalar + $bound, + SA: Storage, + SB: Storage, + SA::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; + + #[inline] + fn $method(self, right: &'b Matrix) -> Self::Output { + self.clone_owned().$method(right) + } + } + + impl<'b, N, R1, C1, R2, C2, SA, SB> $TraitAssign<&'b Matrix> for Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N: Scalar + $bound, + SA: StorageMut, + SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + #[inline] + fn $method_assign(&mut self, right: &'b Matrix) { + assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); + for (left, right) in self.iter_mut().zip(right.iter()) { + left.$method_assign(*right) + } + } + } + + impl $TraitAssign> for Matrix + where R1: Dim, C1: Dim, R2: Dim, C2: Dim, + N: Scalar + $bound, + SA: StorageMut, + SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + #[inline] + fn $method_assign(&mut self, right: Matrix) { + self.$method_assign(&right) + } + } + } +); + +componentwise_binop_impl!(Add, add, ClosedAdd; AddAssign, add_assign); +componentwise_binop_impl!(Sub, sub, ClosedSub; SubAssign, sub_assign); + + + +/* + * + * Multiplication + * + */ + +// Matrix × Scalar +// Matrix / Scalar +macro_rules! componentwise_scalarop_impl( + ($Trait: ident, $method: ident, $bound: ident; + $TraitAssign: ident, $method_assign: ident) => { + impl $Trait for Matrix + where N: Scalar + $bound, + S: Storage { + type Output = OwnedMatrix; + + #[inline] + fn $method(self, rhs: N) -> Self::Output { + let mut res = self.into_owned(); + + for left in res.iter_mut() { + *left = left.$method(rhs) + } + + res + } + } + + impl<'a, N, R: Dim, C: Dim, S> $Trait for &'a Matrix + where N: Scalar + $bound, + S: Storage { + type Output = OwnedMatrix; + + #[inline] + fn $method(self, rhs: N) -> Self::Output { + self.clone_owned().$method(rhs) + } + } + + impl $TraitAssign for Matrix + where N: Scalar + $bound, + S: StorageMut { + #[inline] + fn $method_assign(&mut self, right: N) { + for left in self.iter_mut() { + left.$method_assign(right) + } + } + } + } +); + +componentwise_scalarop_impl!(Mul, mul, ClosedMul; MulAssign, mul_assign); +componentwise_scalarop_impl!(Div, div, ClosedDiv; DivAssign, div_assign); + +macro_rules! left_scalar_mul_impl( + ($($T: ty),* $(,)*) => {$( + impl Mul> for $T + where S: Storage<$T, R, C> { + type Output = OwnedMatrix<$T, R, C, S::Alloc>; + + #[inline] + fn mul(self, right: Matrix<$T, R, C, S>) -> Self::Output { + let mut res = right.into_owned(); + + for right in res.iter_mut() { + *right = self * *right + } + + res + } + } + + impl<'b, R: Dim, C: Dim, S> Mul<&'b Matrix<$T, R, C, S>> for $T + where S: Storage<$T, R, C> { + type Output = OwnedMatrix<$T, R, C, S::Alloc>; + + #[inline] + fn mul(self, right: &'b Matrix<$T, R, C, S>) -> Self::Output { + self * right.clone_owned() + } + } + )*} +); + +left_scalar_mul_impl!( + u8, u16, u32, u64, usize, + i8, i16, i32, i64, isize, + f32, f64 +); + + + +// Matrix × Matrix +impl<'a, 'b, N, R1: Dim, C1: Dim, R2: Dim, C2: Dim, SA, SB> Mul<&'b Matrix> +for &'a Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul, + SB: Storage, + SA: Storage, + SA::Alloc: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMul; + + #[inline] + fn mul(self, right: &'b Matrix) -> Self::Output { + let (nrows1, ncols1) = self.shape(); + let (nrows2, ncols2) = right.shape(); + + assert!(ncols1 == nrows2, "Matrix multiplication dimensions mismatch."); + + let mut res: MatrixMul = unsafe { + Matrix::new_uninitialized_generic(self.data.shape().0, right.data.shape().1) + }; + + for i in 0 .. nrows1 { + for j in 0 .. ncols2 { + let mut acc = N::zero(); + + unsafe { + for k in 0 .. ncols1 { + acc = acc + *self.get_unchecked(i, k) * *right.get_unchecked(k, j); + } + + *res.get_unchecked_mut(i, j) = acc; + } + } + } + + res + } +} + +impl<'a, N, R1: Dim, C1: Dim, R2: Dim, C2: Dim, SA, SB> Mul> +for &'a Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul, + SB: Storage, + SA: Storage, + SA::Alloc: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMul; + + #[inline] + fn mul(self, right: Matrix) -> Self::Output { + self * &right + } +} + +impl<'b, N, R1: Dim, C1: Dim, R2: Dim, C2: Dim, SA, SB> Mul<&'b Matrix> +for Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul, + SB: Storage, + SA: Storage, + SA::Alloc: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMul; + + #[inline] + fn mul(self, right: &'b Matrix) -> Self::Output { + &self * right + } +} + +impl Mul> +for Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul, + SB: Storage, + SA: Storage, + SA::Alloc: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMul; + + #[inline] + fn mul(self, right: Matrix) -> Self::Output { + &self * &right + } +} + +// FIXME: this is too restrictive: +// − we can't use `a *= b` when `a` is a mutable slice. +// − we can't use `a *= b` when C2 is not equal to C1. +impl MulAssign> for Matrix + where R1: Dim, C1: Dim, R2: Dim, + N: Scalar + Zero + ClosedAdd + ClosedMul, + SB: Storage, + SA: OwnedStorage, + ShapeConstraint: AreMultipliable, + SA::Alloc: OwnedAllocator { + #[inline] + fn mul_assign(&mut self, right: Matrix) { + *self = &*self * right + } +} + +impl<'b, N, R1, C1, R2, SA, SB> MulAssign<&'b Matrix> for Matrix + where R1: Dim, C1: Dim, R2: Dim, + N: Scalar + Zero + ClosedAdd + ClosedMul, + SB: Storage, + SA: OwnedStorage, + ShapeConstraint: AreMultipliable, + // FIXME: this is too restrictive. See comments for the non-ref version. + SA::Alloc: OwnedAllocator { + #[inline] + fn mul_assign(&mut self, right: &'b Matrix) { + *self = &*self * right + } +} + + +impl Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul, + SA: Storage { + /// Equivalent to `self.transpose() * right`. + #[inline] + pub fn tr_mul(&self, right: &Matrix) -> MatrixTrMul + where SB: Storage, + SA::Alloc: Allocator, + ShapeConstraint: AreMultipliable { + let (nrows1, ncols1) = self.shape(); + let (nrows2, ncols2) = right.shape(); + + assert!(nrows1 == nrows2, "Matrix multiplication dimensions mismatch."); + + let mut res: MatrixTrMul = unsafe { + Matrix::new_uninitialized_generic(self.data.shape().1, right.data.shape().1) + }; + + for i in 0 .. ncols1 { + for j in 0 .. ncols2 { + let mut acc = N::zero(); + + unsafe { + for k in 0 .. nrows1 { + acc = acc + *self.get_unchecked(k, i) * *right.get_unchecked(k, j); + } + + *res.get_unchecked_mut(i, j) = acc; + } + } + } + + res + } +} diff --git a/src/core/properties.rs b/src/core/properties.rs new file mode 100644 index 00000000..77a28c73 --- /dev/null +++ b/src/core/properties.rs @@ -0,0 +1,107 @@ +// Matrix properties checks. +use num::{Zero, One}; +use approx::ApproxEq; + +use alga::general::{ClosedAdd, ClosedMul, ClosedSub, Field}; + +use core::{Scalar, Matrix, SquareMatrix}; +use core::dimension::Dim; +use core::storage::Storage; + + +impl> Matrix { + /// Indicates if this is a square matrix. + #[inline] + pub fn is_square(&self) -> bool { + let shape = self.shape(); + shape.0 == shape.1 + } +} + +impl> Matrix + // FIXME: ApproxEq prevents us from using those methods on integer matrices… + where N: ApproxEq, + N::Epsilon: Copy { + /// Indicated if this is the identity matrix within a relative error of `eps`. + /// + /// If the matrix is diagonal, this checks that diagonal elements (i.e. at coordinates `(i, i)` + /// for i from `0` to `min(R, C)`) are equal one; and that all other elements are zero. + #[inline] + pub fn is_identity(&self, eps: N::Epsilon) -> bool + where N: Zero + One { + let (nrows, ncols) = self.shape(); + let d; + + if nrows > ncols { + d = ncols; + + for i in d .. nrows { + for j in 0 .. ncols { + if !relative_eq!(self[(i, j)], N::zero(), epsilon = eps) { + return false; + } + } + } + } + else { // nrows <= ncols + d = nrows; + + for i in 0 .. nrows { + for j in d .. ncols { + if !relative_eq!(self[(i, j)], N::zero(), epsilon = eps) { + return false; + } + } + } + } + + // Off-diagonal elements of the sub-square matrix. + for i in 1 .. d { + for j in 0 .. i { + // FIXME: use unsafe indexing. + if !relative_eq!(self[(i, j)], N::zero(), epsilon = eps) || + !relative_eq!(self[(j, i)], N::zero(), epsilon = eps) { + return false; + } + } + } + + // Diagonal elements of the sub-square matrix. + for i in 0 .. d { + if !relative_eq!(self[(i, i)], N::one(), epsilon = eps) { + return false; + } + } + + true + } +} + + +impl> SquareMatrix + where N: Zero + One + ClosedAdd + ClosedMul, + N::Epsilon: Copy { + /// Checks that this matrix is orthogonal, i.e., that it is square and `M × Mᵀ = Id`. + /// + /// In this definition `Id` is approximately equal to the identity matrix with a relative error + /// equal to `eps`. + #[inline] + pub fn is_orthogonal(&self, eps: N::Epsilon) -> bool { + self.is_square() && (self.tr_mul(self)).is_identity(eps) + } + + /// Checks that this matrix is orthogonal and has a determinant equal to 1. + #[inline] + pub fn is_special_orthogonal(&self, eps: N::Epsilon) -> bool + where N: ClosedSub + PartialOrd { + self.is_orthogonal(eps) && self.determinant() > N::zero() + } + + /// Returns `true` if this matrix is invertible. + #[inline] + pub fn is_invertible(&self) -> bool + where N: Field { + // FIXME: improve this? + self.clone_owned().try_inverse().is_some() + } +} diff --git a/src/core/scalar.rs b/src/core/scalar.rs new file mode 100644 index 00000000..14c786d4 --- /dev/null +++ b/src/core/scalar.rs @@ -0,0 +1,9 @@ +use std::fmt::Debug; +use std::any::Any; + +/// The basic scarar type for all structures of `nalgebra`. +/// +/// This does not make any assumption on the algebraic properties of `Self`. +pub trait Scalar: Copy + PartialEq + Debug + Any { +} +impl Scalar for T { } diff --git a/src/core/storage.rs b/src/core/storage.rs new file mode 100644 index 00000000..a3f98a43 --- /dev/null +++ b/src/core/storage.rs @@ -0,0 +1,176 @@ +use std::mem; +use std::any::Any; + +use core::Scalar; +use dimension::Dim; +use allocator::{Allocator, SameShapeR, SameShapeC}; + +/* + * Aliases for sum storage. + */ +/// The data storage for the sum of two matrices with dimensions `(R1, C1)` and `(R2, C2)`. +pub type SumStorage = + <>::Alloc as Allocator, SameShapeC>>::Buffer; + +/* + * Aliases for multiplication storage. + */ +/// The data storage for the multiplication of two matrices with dimensions `(R1, C1)` on the left +/// hand side, and with `C2` columns on the right hand side. +pub type MulStorage = + <>::Alloc as Allocator>::Buffer; + +/// The data storage for the multiplication of two matrices with dimensions `(R1, C1)` on the left +/// hand side, and with `C2` columns on the right hand side. The first matrix is implicitly +/// transposed. +pub type TrMulStorage = + <>::Alloc as Allocator>::Buffer; + +/* + * Alias for allocation result. + */ +/// The owned data storage that can be allocated from `S`. +pub type Owned = + >::Buffer; + + +/// The trait shared by all matrix data storage. +/// +/// FIXME: doc +/// +/// Note that `Self` must always have a number of elements compatible with the matrix length (given +/// by `R` and `C` if they are known at compile-time). For example, implementors of this trait +/// should **not** allow the user to modify the size of the underlying buffer with safe methods +/// (for example the `MatrixVec::data_mut` method is unsafe because the user could change the +/// vector's size so that it no longer contains enough elements: this will lead to UB. +pub unsafe trait Storage: Sized { + /// The static stride of this storage's rows. + type RStride: Dim; + + /// The static stride of this storage's columns. + type CStride: Dim; + + /// The allocator for this family of storage. + type Alloc: Allocator; + + /// Builds a matrix data storage that does not contain any reference. + fn into_owned(self) -> Owned; + + /// Clones this data storage into one that does not contain any reference. + fn clone_owned(&self) -> Owned; + + /// The matrix data pointer. + fn ptr(&self) -> *const N; + + /// The dimension of the matrix at run-time. Arr length of zero indicates the additive identity + /// element of any dimension. Must be equal to `Self::dimension()` if it is not `None`. + fn shape(&self) -> (R, C); + + /// The spacing between concecutive row elements and consecutive column elements. + /// + /// For example this returns `(1, 5)` for a row-major matrix with 5 columns. + fn strides(&self) -> (Self::RStride, Self::CStride); + + /// Compute the index corresponding to the irow-th row and icol-th column of this matrix. The + /// index must be such that the following holds: + /// + /// ```.ignore + /// let lindex = self.linear_index(irow, icol); + /// assert!(*self.get_unchecked(irow, icol) == *self.get_unchecked_linear(lindex) + /// ``` + #[inline] + fn linear_index(&self, irow: usize, icol: usize) -> usize { + let (rstride, cstride) = self.strides(); + + irow * rstride.value() + icol * cstride.value() + } + + /// Gets the address of the i-th matrix component without performing bound-checking. + #[inline] + unsafe fn get_address_unchecked_linear(&self, i: usize) -> *const N { + self.ptr().offset(i as isize) + } + + /// Gets the address of the i-th matrix component without performing bound-checking. + #[inline] + unsafe fn get_address_unchecked(&self, irow: usize, icol: usize) -> *const N { + self.get_address_unchecked_linear(self.linear_index(irow, icol)) + } + + /// Retrieves a reference to the i-th element without bound-checking. + #[inline] + unsafe fn get_unchecked_linear(&self, i: usize) -> &N { + &*self.get_address_unchecked_linear(i) + } + + /// Retrieves a reference to the i-th element without bound-checking. + #[inline] + unsafe fn get_unchecked(&self, irow: usize, icol: usize) -> &N { + self.get_unchecked_linear(self.linear_index(irow, icol)) + } +} + +pub unsafe trait StorageMut: Storage { + /// The matrix mutable data pointer. + fn ptr_mut(&mut self) -> *mut N; + + /// Gets the mutable address of the i-th matrix component without performing bound-checking. + #[inline] + unsafe fn get_address_unchecked_linear_mut(&mut self, i: usize) -> *mut N { + self.ptr_mut().offset(i as isize) + } + + /// Gets the mutable address of the i-th matrix component without performing bound-checking. + #[inline] + unsafe fn get_address_unchecked_mut(&mut self, irow: usize, icol: usize) -> *mut N { + let lid = self.linear_index(irow, icol); + self.get_address_unchecked_linear_mut(lid) + } + + /// Retrieves a mutable reference to the i-th element without bound-checking. + unsafe fn get_unchecked_linear_mut(&mut self, i: usize) -> &mut N { + &mut *self.get_address_unchecked_linear_mut(i) + } + + /// Retrieves a mutable reference to the element at `(irow, icol)` without bound-checking. + #[inline] + unsafe fn get_unchecked_mut(&mut self, irow: usize, icol: usize) -> &mut N { + &mut *self.get_address_unchecked_mut(irow, icol) + } + + /// Swaps two elements using their linear index without bound-checking. + #[inline] + unsafe fn swap_unchecked_linear(&mut self, i1: usize, i2: usize) { + let a = self.get_address_unchecked_linear_mut(i1); + let b = self.get_address_unchecked_linear_mut(i2); + + mem::swap(&mut *a, &mut *b); + } + + /// Swaps two elements without bound-checking. + #[inline] + unsafe fn swap_unchecked(&mut self, row_col1: (usize, usize), row_col2: (usize, usize)) { + let lid1 = self.linear_index(row_col1.0, row_col1.1); + let lid2 = self.linear_index(row_col2.0, row_col2.1); + + self.swap_unchecked_linear(lid1, lid2) + } +} + +/// A matrix storage that does not contain any reference and that is stored contiguously in memory. +/// +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value +/// `.get_unchecked_linear` succeeds. This trait is unsafe because failing to comply to this may +/// cause Undefined Behaviors. +pub unsafe trait OwnedStorage: StorageMut + Clone + Any + where Self::Alloc: Allocator { + // NOTE: We could auto-impl those two methods but we don't to make sure the user is aware that + // data must be contiguous. + /// Converts this data storage to a slice. + #[inline] + fn as_slice(&self) -> &[N]; + + /// Converts this data storage to a mutable slice. + #[inline] + fn as_mut_slice(&mut self) -> &mut [N]; +} diff --git a/src/core/unit.rs b/src/core/unit.rs new file mode 100644 index 00000000..9c35bed0 --- /dev/null +++ b/src/core/unit.rs @@ -0,0 +1,165 @@ +use std::ops::Neg; +use approx::ApproxEq; + +use alga::general::SubsetOf; +use alga::linear::NormedSpace; + + +/// A wrapper that ensures the undelying algebraic entity has a unit norm. +/// +/// Use `.as_ref()` or `.unwrap()` to obtain the undelying value by-reference or by-move. +#[repr(C)] +#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)] +pub struct Unit { + value: T +} + +impl Unit { + /// Normalize the given value and return it wrapped on a `Unit` structure. + #[inline] + pub fn new_normalize(value: T) -> Self { + Self::new_and_get(value).0 + } + + /// Attempts to normalize the given value and return it wrapped on a `Unit` structure. + /// + /// Returns `None` if the norm was smaller or equal to `min_norm`. + #[inline] + pub fn try_new(value: T, min_norm: T::Field) -> Option { + Self::try_new_and_get(value, min_norm).map(|res| res.0) + } + + /// Normalize the given value and return it wrapped on a `Unit` structure and its norm. + #[inline] + pub fn new_and_get(mut value: T) -> (Self, T::Field) { + let n = value.normalize_mut(); + + (Unit { value: value }, n) + } + + /// Normalize the given value and return it wrapped on a `Unit` structure and its norm. + /// + /// Returns `None` if the norm was smaller or equal to `min_norm`. + #[inline] + pub fn try_new_and_get(mut value: T, min_norm: T::Field) -> Option<(Self, T::Field)> { + if let Some(n) = value.try_normalize_mut(min_norm) { + Some((Unit { value: value }, n)) + } + else { + None + } + } + + /// Normalizes this value again. This is useful when repeated computations + /// might cause a drift in the norm because of float inaccuracies. + /// + /// Returns the norm before re-normalization (should be close to `1.0`). + #[inline] + pub fn renormalize(&mut self) -> T::Field { + self.value.normalize_mut() + } +} + +impl Unit { + /// Wraps the given value, assuming it is already normalized. + /// + /// This function is not safe because `value` is not verified to be actually normalized. + #[inline] + pub fn new_unchecked(value: T) -> Self { + Unit { value: value } + } + + /// Retrieves the underlying value. + #[inline] + pub fn unwrap(self) -> T { + self.value + } + + /// Returns a mutable reference to the underlying value. This is `_unchecked` because modifying + /// the underlying value in such a way that it no longer has unit length may lead to unexpected + /// results. + #[inline] + pub fn as_mut_unchecked(&mut self) -> &mut T { + &mut self.value + } +} + +impl AsRef for Unit { + #[inline] + fn as_ref(&self) -> &T { + &self.value + } +} + +/* + * + * Conversions. + * + */ +impl SubsetOf for Unit +where T::Field: ApproxEq { + #[inline] + fn to_superset(&self) -> T { + self.clone().unwrap() + } + + #[inline] + fn is_in_subset(value: &T) -> bool { + relative_eq!(value.norm_squared(), ::one()) + } + + #[inline] + unsafe fn from_superset_unchecked(value: &T) -> Self { + Unit::new_normalize(value.clone()) // We still need to re-normalize because the condition is inexact. + } +} + +impl ApproxEq for Unit { + type Epsilon = T::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + T::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.value.relative_eq(&other.value, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.value.ulps_eq(&other.value, epsilon, max_ulps) + } +} + + +// FIXME:re-enable this impl when spacialization is possible. +// Currently, it is disabled so that we can have a nice output for the `UnitQuaternion` display. +/* +impl fmt::Display for Unit { + // XXX: will not always work correctly due to rounding errors. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value.fmt(f) + } +} +*/ + +impl Neg for Unit { + type Output = Unit; + + #[inline] + fn neg(self) -> Self::Output { + Unit::new_unchecked(-self.value) + } +} diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs new file mode 100644 index 00000000..7b5fc6ee --- /dev/null +++ b/src/geometry/isometry.rs @@ -0,0 +1,195 @@ +use std::fmt; +use std::marker::PhantomData; +use approx::ApproxEq; + +use alga::general::{Real, SubsetOf}; +use alga::linear::Rotation; + +use core::{Scalar, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::{Storage, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; +use geometry::{TranslationBase, PointBase}; + +/// A direct isometry, i.e., a rotation followed by a translation. +#[repr(C)] +#[derive(Hash, Debug, Clone, Copy)] +pub struct IsometryBase { + pub rotation: R, + pub translation: TranslationBase, + // One private field just to prevent explicit construction. + _noconstruct: PhantomData +} + +impl IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + /// Creates a new isometry from its rotational and translational parts. + #[inline] + pub fn from_parts(translation: TranslationBase, rotation: R) -> IsometryBase { + IsometryBase { + rotation: rotation, + translation: translation, + _noconstruct: PhantomData + } + } + + /// Inverts `self`. + #[inline] + pub fn inverse(&self) -> IsometryBase { + let mut res = self.clone(); + res.inverse_mut(); + res + } + + /// Inverts `self`. + #[inline] + pub fn inverse_mut(&mut self) { + self.rotation.inverse_mut(); + self.translation.inverse_mut(); + self.translation.vector = self.rotation.transform_vector(&self.translation.vector); + } + + /// Appends to `self` the given translation in-place. + #[inline] + pub fn append_translation_mut(&mut self, t: &TranslationBase) { + self.translation.vector += &t.vector + } + + /// Appends to `self` the given rotation in-place. + #[inline] + pub fn append_rotation_mut(&mut self, r: &R) { + self.rotation = self.rotation.append_rotation(&r); + self.translation.vector = r.transform_vector(&self.translation.vector); + } + + /// Appends in-place to `self` a rotation centered at the point `p`, i.e., the rotation that + /// lets `p` invariant. + #[inline] + pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &PointBase) { + self.translation.vector -= &p.coords; + self.append_rotation_mut(r); + self.translation.vector += &p.coords; + } + + /// Appends in-place to `self` a rotation centered at the point with coordinates + /// `self.translation`. + #[inline] + pub fn append_rotation_wrt_center_mut(&mut self, r: &R) { + let center = PointBase::from_coordinates(self.translation.vector.clone()); + self.append_rotation_wrt_point_mut(r, ¢er) + } +} + +// NOTE: we don't require `R: Rotation<...>` here because this is not useful for the implementation +// and makes it hard to use it, e.g., for Transform × Isometry implementation. +// This is OK since all constructors of the isometry enforce the Rotation bound already (and +// explicit struct construction is prevented by the dummy ZST field). +impl IsometryBase + where N: Scalar, + S: Storage { + /// Converts this isometry into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + where D: DimNameAdd, + R: SubsetOf, S::Alloc>>, + S::Alloc: Allocator, DimNameSum> { + let mut res: OwnedSquareMatrix = ::convert_ref(&self.rotation); + res.fixed_slice_mut::(0, D::dim()).copy_from(&self.translation.vector); + + res + } +} + + +impl Eq for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation> + Eq, + S::Alloc: OwnedAllocator { +} + +impl PartialEq for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation> + PartialEq, + S::Alloc: OwnedAllocator { + #[inline] + fn eq(&self, right: &IsometryBase) -> bool { + self.translation == right.translation && + self.rotation == right.rotation + } +} + +impl ApproxEq for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation> + ApproxEq, + S::Alloc: OwnedAllocator, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.translation.relative_eq(&other.translation, epsilon, max_relative) && + self.rotation.relative_eq(&other.rotation, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.translation.ulps_eq(&other.translation, epsilon, max_ulps) && + self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps) + } +} + +/* + * + * Display + * + */ +impl fmt::Display for IsometryBase + where N: Real + fmt::Display, + S: OwnedStorage, + R: fmt::Display, + S::Alloc: OwnedAllocator + Allocator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let precision = f.precision().unwrap_or(3); + + try!(writeln!(f, "IsometryBase {{")); + try!(write!(f, "{:.*}", precision, self.translation)); + try!(write!(f, "{:.*}", precision, self.rotation)); + writeln!(f, "}}") + } +} + + +// /* +// * +// * Absolute +// * +// */ +// impl Absolute for $t { +// type AbsoluteValue = $submatrix; +// +// #[inline] +// fn abs(m: &$t) -> $submatrix { +// Absolute::abs(&m.submatrix) +// } +// } diff --git a/src/geometry/isometry_alga.rs b/src/geometry/isometry_alga.rs new file mode 100644 index 00000000..8728d9ac --- /dev/null +++ b/src/geometry/isometry_alga.rs @@ -0,0 +1,198 @@ +use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, + AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; +use alga::linear::{Transformation, Similarity, AffineTransformation, DirectIsometry, Isometry, + Rotation, ProjectiveTransformation}; + +use core::ColumnVector; +use core::dimension::{DimName, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{IsometryBase, TranslationBase, PointBase}; + + +/* + * + * Algebraic structures. + * + */ +impl Identity for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl Inverse for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Self { + self.inverse() + } + + #[inline] + fn inverse_mut(&mut self) { + self.inverse_mut() + } +} + +impl AbstractMagma for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +macro_rules! impl_multiplicative_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl $marker<$operator> for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { } + )*} +); + +impl_multiplicative_structures!( + AbstractSemigroup, + AbstractMonoid, + AbstractQuasigroup, + AbstractLoop, + AbstractGroup +); + +/* + * + * Transformation groups. + * + */ +impl Transformation> for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + self * pt + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self * v + } +} + +impl ProjectiveTransformation> for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + self.rotation.inverse_transform_point(&(pt - &self.translation.vector)) + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self.rotation.inverse_transform_vector(v) + } +} + +impl AffineTransformation> for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + type Rotation = R; + type NonUniformScaling = Id; + type Translation = TranslationBase; + + #[inline] + fn decompose(&self) -> (TranslationBase, R, Id, R) { + (self.translation.clone(), self.rotation.clone(), Id::new(), R::identity()) + } + + #[inline] + fn append_translation(&self, t: &Self::Translation) -> Self { + t * self + } + + #[inline] + fn prepend_translation(&self, t: &Self::Translation) -> Self { + self * t + } + + #[inline] + fn append_rotation(&self, r: &Self::Rotation) -> Self { + let shift = r.transform_vector(&self.translation.vector); + IsometryBase::from_parts(TranslationBase::from_vector(shift), r.clone() * self.rotation.clone()) + } + + #[inline] + fn prepend_rotation(&self, r: &Self::Rotation) -> Self { + self * r + } + + #[inline] + fn append_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } + + #[inline] + fn prepend_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } + + #[inline] + fn append_rotation_wrt_point(&self, r: &Self::Rotation, p: &PointBase) -> Option { + let mut res = self.clone(); + res.append_rotation_wrt_point_mut(r, p); + Some(res) + } +} + +impl Similarity> for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + type Scaling = Id; + + #[inline] + fn translation(&self) -> TranslationBase { + self.translation.clone() + } + + #[inline] + fn rotation(&self) -> R { + self.rotation.clone() + } + + #[inline] + fn scaling(&self) -> Id { + Id::new() + } +} + +macro_rules! marker_impl( + ($($Trait: ident),*) => {$( + impl $Trait> for IsometryBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { } + )*} +); + +marker_impl!(Isometry, DirectIsometry); diff --git a/src/geometry/isometry_alias.rs b/src/geometry/isometry_alias.rs new file mode 100644 index 00000000..0bf44b4a --- /dev/null +++ b/src/geometry/isometry_alias.rs @@ -0,0 +1,19 @@ +use core::MatrixArray; +use core::dimension::{U1, U2, U3}; + +use geometry::{Rotation, IsometryBase, UnitQuaternion, UnitComplex}; + +/// A D-dimensional isometry. +pub type Isometry = IsometryBase, Rotation>; + +/// A 2-dimensional isometry using a unit complex number for its rotational part. +pub type Isometry2 = IsometryBase, UnitComplex>; + +/// A 3-dimensional isometry using a unit quaternion for its rotational part. +pub type Isometry3 = IsometryBase, UnitQuaternion>; + +/// A 2-dimensional isometry using a rotation matrix for its rotation part. +pub type IsometryMatrix2 = Isometry; + +/// A 3-dimensional isometry using a rotation matrix for its rotation part. +pub type IsometryMatrix3 = Isometry; diff --git a/src/geometry/isometry_construction.rs b/src/geometry/isometry_construction.rs new file mode 100644 index 00000000..9eb3a42f --- /dev/null +++ b/src/geometry/isometry_construction.rs @@ -0,0 +1,195 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use num::One; +use rand::{Rng, Rand}; + +use alga::general::Real; +use alga::linear::Rotation as AlgaRotation; + +use core::ColumnVector; +use core::dimension::{DimName, U1, U2, U3, U4}; +use core::allocator::{OwnedAllocator, Allocator}; +use core::storage::OwnedStorage; + +use geometry::{PointBase, TranslationBase, RotationBase, IsometryBase, UnitQuaternionBase, UnitComplex}; + + +impl IsometryBase + where N: Real, + S: OwnedStorage, + R: AlgaRotation>, + S::Alloc: OwnedAllocator { + /// Creates a new identity isometry. + #[inline] + pub fn identity() -> Self { + Self::from_parts(TranslationBase::identity(), R::identity()) + } +} + +impl One for IsometryBase + where N: Real, + S: OwnedStorage, + R: AlgaRotation>, + S::Alloc: OwnedAllocator { + /// Creates a new identity isometry. + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Rand for IsometryBase + where N: Real + Rand, + S: OwnedStorage, + R: AlgaRotation> + Rand, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut G) -> Self { + Self::from_parts(rng.gen(), rng.gen()) + } +} + +impl IsometryBase + where N: Real, + S: OwnedStorage, + R: AlgaRotation>, + S::Alloc: OwnedAllocator { + /// The isometry that applies the rotation `r` with its axis passing through the point `p`. + /// This effectively lets `p` invariant. + #[inline] + pub fn rotation_wrt_point(r: R, p: PointBase) -> Self { + let shift = r.transform_vector(&-&p.coords); + Self::from_parts(TranslationBase::from_vector(shift + p.coords), r) + } +} + +#[cfg(feature = "arbitrary")] +impl Arbitrary for IsometryBase + where N: Real + Arbitrary + Send, + S: OwnedStorage + Send, + R: AlgaRotation> + Arbitrary + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(rng: &mut G) -> Self { + Self::from_parts(Arbitrary::arbitrary(rng), Arbitrary::arbitrary(rng)) + } +} + +/* + * + * Constructors for various static dimensions. + * + */ + +// 2D rotation. +impl IsometryBase> + where N: Real, + S: OwnedStorage, + SR: OwnedStorage, + S::Alloc: OwnedAllocator, + SR::Alloc: OwnedAllocator { + /// Creates a new isometry from a translation and a rotation angle. + #[inline] + pub fn new(translation: ColumnVector, angle: N) -> Self { + Self::from_parts(TranslationBase::from_vector(translation), RotationBase::::new(angle)) + } +} + +impl IsometryBase> + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new isometry from a translation and a rotation angle. + #[inline] + pub fn new(translation: ColumnVector, angle: N) -> Self { + Self::from_parts(TranslationBase::from_vector(translation), UnitComplex::from_angle(angle)) + } +} + +// 3D rotation. +macro_rules! isometry_construction_impl( + ($RotId: ident < $($RotParams: ident),*>, $RRDim: ty, $RCDim: ty) => { + impl IsometryBase> + where N: Real, + S: OwnedStorage, + SR: OwnedStorage, + S::Alloc: OwnedAllocator, + SR::Alloc: OwnedAllocator + + Allocator { + /// Creates a new isometry from a translation and a rotation axis-angle. + #[inline] + pub fn new(translation: ColumnVector, axisangle: ColumnVector) -> Self { + Self::from_parts( + TranslationBase::from_vector(translation), + $RotId::<$($RotParams),*>::from_scaled_axis(axisangle)) + } + + /// Creates an isometry that corresponds to the local frame of an observer standing at the + /// point `eye` and looking toward `target`. + /// + /// It maps the view direction `target - eye` to the positive `z` axis and the origin to the + /// `eye`. + /// + /// # Arguments + /// * eye - The observer position. + /// * target - The target position. + /// * up - Vertical direction. The only requirement of this parameter is to not be collinear + /// to `eye - at`. Non-collinearity is not checked. + #[inline] + pub fn new_observer_frame(eye: &PointBase, + target: &PointBase, + up: &ColumnVector) + -> Self { + Self::from_parts( + TranslationBase::from_vector(eye.coords.clone()), + $RotId::new_observer_frame(&(target - eye), up)) + } + + /// Builds a right-handed look-at view matrix. + /// + /// This conforms to the common notion of right handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_rh(eye: &PointBase, + target: &PointBase, + up: &ColumnVector) + -> Self { + let rotation = $RotId::look_at_rh(&(target - eye), up); + let trans = &rotation * (-eye); + + Self::from_parts(TranslationBase::from_vector(trans.coords), rotation) + } + + /// Builds a left-handed look-at view matrix. + /// + /// This conforms to the common notion of left handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_lh(eye: &PointBase, + target: &PointBase, + up: &ColumnVector) + -> Self { + let rotation = $RotId::look_at_lh(&(target - eye), up); + let trans = &rotation * (-eye); + + Self::from_parts(TranslationBase::from_vector(trans.coords), rotation) + } + } + } +); + +isometry_construction_impl!(RotationBase, U3, U3); +isometry_construction_impl!(UnitQuaternionBase, U4, U1); diff --git a/src/geometry/isometry_conversion.rs b/src/geometry/isometry_conversion.rs new file mode 100644 index 00000000..c71b7289 --- /dev/null +++ b/src/geometry/isometry_conversion.rs @@ -0,0 +1,163 @@ +use alga::general::{Real, SubsetOf, SupersetOf}; +use alga::linear::Rotation; + +use core::{SquareMatrix, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; + +use geometry::{PointBase, TranslationBase, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; + +/* + * This file provides the following conversions: + * ============================================= + * + * IsometryBase -> IsometryBase + * IsometryBase -> SimilarityBase + * IsometryBase -> TransformBase + * IsometryBase -> Matrix (homogeneous) + */ + + +impl SubsetOf> for IsometryBase + where N1: Real, + N2: Real + SupersetOf, + R1: Rotation> + SubsetOf, + R2: Rotation>, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> IsometryBase { + IsometryBase::from_parts( + self.translation.to_superset(), + self.rotation.to_superset() + ) + } + + #[inline] + fn is_in_subset(iso: &IsometryBase) -> bool { + ::is_convertible::<_, TranslationBase>(&iso.translation) && + ::is_convertible::<_, R1>(&iso.rotation) + } + + #[inline] + unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + IsometryBase::from_parts( + iso.translation.to_subset_unchecked(), + iso.rotation.to_subset_unchecked() + ) + } +} + + +impl SubsetOf> for IsometryBase + where N1: Real, + N2: Real + SupersetOf, + R1: Rotation> + SubsetOf, + R2: Rotation>, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> SimilarityBase { + SimilarityBase::from_isometry( + self.to_superset(), + N2::one() + ) + } + + #[inline] + fn is_in_subset(sim: &SimilarityBase) -> bool { + ::is_convertible::<_, IsometryBase>(&sim.isometry) && + sim.scaling() == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + ::convert_ref_unchecked(&sim.isometry) + } +} + + +impl SubsetOf> for IsometryBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + C: SuperTCategoryOf, + R: Rotation> + + SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() + SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum>, // needed by R + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + // needed by: mm.fixed_slice_mut + Allocator + // needed by: m.fixed_slice + Allocator { // needed by: m.fixed_slice + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf, SB>> for IsometryBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + R: Rotation> + + SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() + SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum>, // needed by R + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + // needed by: mm.fixed_slice_mut + Allocator + // needed by: m.fixed_slice + Allocator { // needed by: m.fixed_slice + #[inline] + fn to_superset(&self) -> SquareMatrix, SB> { + self.to_homogeneous().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + let rot = m.fixed_slice::(0, 0); + let bottom = m.fixed_slice::(D::dim(), 0); + + // Scalar types agree. + m.iter().all(|e| SupersetOf::::is_in_subset(e)) && + // The block part is a rotation. + rot.is_special_orthogonal(N2::default_epsilon() * ::convert(100.0)) && + // The bottom row is (0, 0, ..., 1) + bottom.iter().all(|e| e.is_zero()) && + m[(D::dim(), D::dim())] == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + let t = m.fixed_slice::(0, D::dim()).into_owned(); + let t = TranslationBase::from_vector(::convert_unchecked(t)); + + Self::from_parts(t, ::convert_unchecked(m.clone_owned())) + } +} diff --git a/src/geometry/isometry_ops.rs b/src/geometry/isometry_ops.rs new file mode 100644 index 00000000..02f924ed --- /dev/null +++ b/src/geometry/isometry_ops.rs @@ -0,0 +1,435 @@ +use std::ops::{Mul, MulAssign, Div, DivAssign}; + +use alga::general::Real; +use alga::linear::Rotation; + +use core::ColumnVector; +use core::dimension::{DimName, U1, U3, U4}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{PointBase, RotationBase, IsometryBase, TranslationBase, UnitQuaternionBase}; + +// FIXME: there are several cloning of rotations that we could probably get rid of (but we didn't +// yet because that would require to add a bound like `where for<'a, 'b> &'a R: Mul<&'b R, Output = R>` +// which is quite ugly. + +/* + * + * In this file, we provide: + * ========================= + * + * + * (Operators) + * + * IsometryBase × IsometryBase + * IsometryBase × R + * + * + * IsometryBase ÷ IsometryBase + * IsometryBase ÷ R + * + * IsometryBase × PointBase + * IsometryBase × ColumnVector + * + * + * IsometryBase × TranslationBase + * TranslationBase × IsometryBase + * TranslationBase × R -> IsometryBase + * + * NOTE: The following are provided explicitly because we can't have R × IsometryBase. + * RotationBase × IsometryBase + * UnitQuaternion × IsometryBase + * + * RotationBase ÷ IsometryBase + * UnitQuaternion ÷ IsometryBase + * + * RotationBase × TranslationBase -> IsometryBase + * UnitQuaternion × TranslationBase -> IsometryBase + * + * + * (Assignment Operators) + * + * IsometryBase ×= TranslationBase + * + * IsometryBase ×= IsometryBase + * IsometryBase ×= R + * + * IsometryBase ÷= IsometryBase + * IsometryBase ÷= R + * + */ + + +macro_rules! isometry_binop_impl( + ($Op: ident, $op: ident; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N, D: DimName, S, R> $Op<$Rhs> for $Lhs + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + type Output = $Output; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + +macro_rules! isometry_binop_impl_all( + ($Op: ident, $op: ident; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + [val val] => $action_val_val: expr; + [ref val] => $action_ref_val: expr; + [val ref] => $action_val_ref: expr; + [ref ref] => $action_ref_ref: expr;) => { + isometry_binop_impl!( + $Op, $op; + $lhs: $Lhs, $rhs: $Rhs, Output = $Output; + $action_val_val; ); + + isometry_binop_impl!( + $Op, $op; + $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Output; + $action_ref_val; 'a); + + isometry_binop_impl!( + $Op, $op; + $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_val_ref; 'b); + + isometry_binop_impl!( + $Op, $op; + $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_ref_ref; 'a, 'b); + } +); + +macro_rules! isometry_binop_assign_impl_all( + ($OpAssign: ident, $op_assign: ident; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; + [val] => $action_val: expr; + [ref] => $action_ref: expr;) => { + impl $OpAssign<$Rhs> for $Lhs + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn $op_assign(&mut $lhs, $rhs: $Rhs) { + $action_val + } + } + + impl<'b, N, D: DimName, S, R> $OpAssign<&'b $Rhs> for $Lhs + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn $op_assign(&mut $lhs, $rhs: &'b $Rhs) { + $action_ref + } + } + } +); + +// IsometryBase × IsometryBase +// IsometryBase ÷ IsometryBase +isometry_binop_impl_all!( + Mul, mul; + self: IsometryBase, rhs: IsometryBase, Output = IsometryBase; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let shift = self.rotation.transform_vector(&rhs.translation.vector); + + IsometryBase::from_parts(TranslationBase::from_vector(&self.translation.vector + shift), + self.rotation.clone() * rhs.rotation.clone()) // FIXME: too bad we have to clone. + }; +); + + +isometry_binop_impl_all!( + Div, div; + self: IsometryBase, rhs: IsometryBase, Output = IsometryBase; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + + +// IsometryBase ×= TranslationBase +isometry_binop_assign_impl_all!( + MulAssign, mul_assign; + self: IsometryBase, rhs: TranslationBase; + [val] => *self *= &rhs; + [ref] => { + let shift = self.rotation.transform_vector(&rhs.vector); + self.translation.vector += shift; + }; +); + +// IsometryBase ×= IsometryBase +// IsometryBase ÷= IsometryBase +isometry_binop_assign_impl_all!( + MulAssign, mul_assign; + self: IsometryBase, rhs: IsometryBase; + [val] => *self *= &rhs; + [ref] => { + let shift = self.rotation.transform_vector(&rhs.translation.vector); + self.translation.vector += shift; + self.rotation *= rhs.rotation.clone(); + }; +); + +isometry_binop_assign_impl_all!( + DivAssign, div_assign; + self: IsometryBase, rhs: IsometryBase; + [val] => *self /= &rhs; + [ref] => *self *= rhs.inverse(); +); + +// IsometryBase ×= R +// IsometryBase ÷= R +isometry_binop_assign_impl_all!( + MulAssign, mul_assign; + self: IsometryBase, rhs: R; + [val] => self.rotation *= rhs; + [ref] => self.rotation *= rhs.clone(); +); + +isometry_binop_assign_impl_all!( + DivAssign, div_assign; + self: IsometryBase, rhs: R; + // FIXME: don't invert explicitly? + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); + + +// IsometryBase × RotationBase +// IsometryBase ÷ RotationBase +isometry_binop_impl_all!( + Mul, mul; + self: IsometryBase, rhs: R, Output = IsometryBase; + [val val] => IsometryBase::from_parts(self.translation, self.rotation * rhs); + [ref val] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() * rhs); // FIXME: do not clone. + [val ref] => IsometryBase::from_parts(self.translation, self.rotation * rhs.clone()); + [ref ref] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() * rhs.clone()); +); + + +isometry_binop_impl_all!( + Div, div; + self: IsometryBase, rhs: R, Output = IsometryBase; + [val val] => IsometryBase::from_parts(self.translation, self.rotation / rhs); + [ref val] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() / rhs); + [val ref] => IsometryBase::from_parts(self.translation, self.rotation / rhs.clone()); + [ref ref] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() / rhs.clone()); +); + + +// IsometryBase × PointBase +isometry_binop_impl_all!( + Mul, mul; + self: IsometryBase, right: PointBase, Output = PointBase; + [val val] => self.translation * self.rotation.transform_point(&right); + [ref val] => &self.translation * self.rotation.transform_point(&right); + [val ref] => self.translation * self.rotation.transform_point(right); + [ref ref] => &self.translation * self.rotation.transform_point(right); +); + + +// IsometryBase × Vector +isometry_binop_impl_all!( + Mul, mul; + self: IsometryBase, right: ColumnVector, Output = ColumnVector; + [val val] => self.rotation.transform_vector(&right); + [ref val] => self.rotation.transform_vector(&right); + [val ref] => self.rotation.transform_vector(right); + [ref ref] => self.rotation.transform_vector(right); +); + + +// IsometryBase × TranslationBase +isometry_binop_impl_all!( + Mul, mul; + self: IsometryBase, right: TranslationBase, Output = IsometryBase; + [val val] => &self * &right; + [ref val] => self * &right; + [val ref] => &self * right; + [ref ref] => { + let new_tr = &self.translation.vector + self.rotation.transform_vector(&right.vector); + IsometryBase::from_parts(TranslationBase::from_vector(new_tr), self.rotation.clone()) + }; +); + +// TranslationBase × IsometryBase +isometry_binop_impl_all!( + Mul, mul; + self: TranslationBase, right: IsometryBase, Output = IsometryBase; + [val val] => IsometryBase::from_parts(self * right.translation, right.rotation); + [ref val] => IsometryBase::from_parts(self * &right.translation, right.rotation); + [val ref] => IsometryBase::from_parts(self * &right.translation, right.rotation.clone()); + [ref ref] => IsometryBase::from_parts(self * &right.translation, right.rotation.clone()); +); + + +// TranslationBase × R +isometry_binop_impl_all!( + Mul, mul; + self: TranslationBase, right: R, Output = IsometryBase; + [val val] => IsometryBase::from_parts(self, right); + [ref val] => IsometryBase::from_parts(self.clone(), right); + [val ref] => IsometryBase::from_parts(self, right.clone()); + [ref ref] => IsometryBase::from_parts(self.clone(), right.clone()); +); + + + + +macro_rules! isometry_from_composition_impl( + ($Op: ident, $op: ident; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(for $Dims: ident: $DimsBound: ident),*; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + type Output = $Output; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + +macro_rules! isometry_from_composition_impl_all( + ($Op: ident, $op: ident; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(for $Dims: ident: $DimsBound: ident),*; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + [val val] => $action_val_val: expr; + [ref val] => $action_ref_val: expr; + [val ref] => $action_val_ref: expr; + [ref ref] => $action_ref_ref: expr;) => { + + isometry_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: $Lhs, $rhs: $Rhs, Output = $Output; + $action_val_val; ); + + isometry_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Output; + $action_ref_val; 'a); + + isometry_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_val_ref; 'b); + + isometry_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_ref_ref; 'a, 'b); + } +); + + +// RotationBase × TranslationBase +isometry_from_composition_impl_all!( + Mul, mul; + (D, D), (D, U1) for D: DimName; + self: RotationBase, right: TranslationBase, Output = IsometryBase>; + [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); + [ref val] => IsometryBase::from_parts(TranslationBase::from_vector(self * right.vector), self.clone()); + [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &right.vector), self); + [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector(self * &right.vector), self.clone()); +); + + +// UnitQuaternionBase × TranslationBase +isometry_from_composition_impl_all!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, right: TranslationBase, + Output = IsometryBase>; + [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); + [ref val] => IsometryBase::from_parts(TranslationBase::from_vector(self * right.vector), self.clone()); + [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &right.vector), self); + [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector(self * &right.vector), self.clone()); +); + +// RotationBase × IsometryBase +isometry_from_composition_impl_all!( + Mul, mul; + (D, D), (D, U1) for D: DimName; + self: RotationBase, right: IsometryBase>, + Output = IsometryBase>; + [val val] => &self * &right; + [ref val] => self * &right; + [val ref] => &self * right; + [ref ref] => { + let shift = self * &right.translation.vector; + IsometryBase::from_parts(TranslationBase::from_vector(shift), self * &right.rotation) + }; +); + +// RotationBase ÷ IsometryBase +isometry_from_composition_impl_all!( + Div, div; + (D, D), (D, U1) for D: DimName; + self: RotationBase, right: IsometryBase>, + Output = IsometryBase>; + // FIXME: don't call iverse explicitly? + [val val] => self * right.inverse(); + [ref val] => self * right.inverse(); + [val ref] => self * right.inverse(); + [ref ref] => self * right.inverse(); +); + + +// UnitQuaternion × IsometryBase +isometry_from_composition_impl_all!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, right: IsometryBase>, + Output = IsometryBase>; + [val val] => &self * &right; + [ref val] => self * &right; + [val ref] => &self * right; + [ref ref] => { + let shift = self * &right.translation.vector; + IsometryBase::from_parts(TranslationBase::from_vector(shift), self * &right.rotation) + }; +); + + +// UnitQuaternion ÷ IsometryBase +isometry_from_composition_impl_all!( + Div, div; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, right: IsometryBase>, + Output = IsometryBase>; + // 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(); +); diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs new file mode 100644 index 00000000..21b51c61 --- /dev/null +++ b/src/geometry/mod.rs @@ -0,0 +1,87 @@ +mod op_macros; + +mod point; +mod point_construction; +mod point_alias; +mod point_ops; +mod point_alga; +mod point_conversion; +mod point_coordinates; + +mod rotation; +mod rotation_construction; +mod rotation_ops; +mod rotation_alga; // FIXME: implement RotationBase methods. +mod rotation_conversion; +mod rotation_alias; +mod rotation_specialization; + +mod quaternion; +mod quaternion_construction; +mod quaternion_ops; +mod quaternion_alga; +mod quaternion_alias; +mod quaternion_coordinates; +mod quaternion_conversion; + +mod unit_complex; +mod unit_complex_construction; +mod unit_complex_ops; +mod unit_complex_alga; + +mod translation; +mod translation_construction; +mod translation_ops; +mod translation_alga; +mod translation_conversion; +mod translation_alias; + +mod isometry; +mod isometry_construction; +mod isometry_ops; +mod isometry_alga; +mod isometry_conversion; +mod isometry_alias; + +mod similarity; +mod similarity_construction; +mod similarity_ops; +mod similarity_alga; +mod similarity_conversion; +mod similarity_alias; + +mod transform; +mod transform_construction; +mod transform_ops; +mod transform_alga; +mod transform_conversion; +mod transform_alias; + +mod orthographic; +mod perspective; + +pub use self::point::*; +pub use self::point_alias::*; + +pub use self::rotation::*; +pub use self::rotation_alias::*; + +pub use self::quaternion::*; +pub use self::quaternion_alias::*; + +pub use self::unit_complex::*; + +pub use self::translation::*; +pub use self::translation_alias::*; + +pub use self::isometry::*; +pub use self::isometry_alias::*; + +pub use self::similarity::*; +pub use self::similarity_alias::*; + +pub use self::transform::*; +pub use self::transform_alias::*; + +pub use self::orthographic::{OrthographicBase, Orthographic3}; +pub use self::perspective::{PerspectiveBase, Perspective3}; diff --git a/src/geometry/op_macros.rs b/src/geometry/op_macros.rs new file mode 100644 index 00000000..c81fd452 --- /dev/null +++ b/src/geometry/op_macros.rs @@ -0,0 +1,188 @@ +#![macro_use] + + +// FIXME: merge with `md_impl`. +/// Macro for the implementation of multiplication and division. +macro_rules! md_impl( + ( + // Operator, operator method, and calar bounds. + $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; + // Storage dimensions, and dimension bounds. + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + // [Optional] Extra allocator bounds. + $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; + // Argument identifiers and types + output. + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; + // Operator actual mplementation. + $action: expr; + // Lifetime. + $($lives: tt),*) => { + impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$BoundParam>)*)*, SA, SB> $Op<$Rhs> for $Lhs + where N: Scalar + Zero + ClosedAdd + ClosedMul $($(+ $ScalarBounds)*)*, + SA: Storage, + SB: Storage, + $( $ConstraintType: $ConstraintBound<$( $ConstraintBoundParams $( = $EqBound )*),*> ),* + { + type Output = $Result; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + + +/// Macro for the implementation of multiplication and division. +/// Implements all the argument reference combinations. +macro_rules! md_impl_all( + ( + // Operator, operator method, and calar bounds. + $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; + // Storage dimensions, and dimension bounds. + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + // [Optional] Extra allocator bounds. + $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; + // Argument identifiers and types + output. + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; + // Operators actual implementations. + [val val] => $action_val_val: expr; + [ref val] => $action_ref_val: expr; + [val ref] => $action_val_ref: expr; + [ref ref] => $action_ref_ref: expr;) => { + + md_impl!( + $Op, $op $(where N: $($ScalarBounds),*)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + $lhs: $Lhs, $rhs: $Rhs, Output = $Result; + $action_val_val; ); + + md_impl!( + $Op, $op $(where N: $($ScalarBounds),*)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Result; + $action_ref_val; 'a); + + md_impl!( + $Op, $op $(where N: $($ScalarBounds),*)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Result; + $action_val_ref; 'b); + + md_impl!( + $Op, $op $(where N: $($ScalarBounds),*)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Result; + $action_ref_ref; 'a, 'b); + } +); + + +/// Macro for the implementation of assignement-multiplication and assignement-division. +macro_rules! md_assign_impl( + ( + // Operator, operator method, and scalar bounds. + $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; + // Storage dimensions, and dimension bounds. + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + // [Optional] Extra allocator bounds. + $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; + // Argument identifiers and types. + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; + // Actual implementation and lifetimes. + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$BoundParam>)*)*, SA, SB> $Op<$Rhs> for $Lhs + where N: Scalar + Zero + ClosedAdd + ClosedMul $($(+ $ScalarBounds)*)*, + SA: OwnedStorage, // FIXME: this is too restrictive. + SB: Storage, + SA::Alloc: OwnedAllocator, + $( $ConstraintType: $ConstraintBound<$( $ConstraintBoundParams $( = $EqBound )*),*> ),* + { + #[inline] + fn $op(&mut $lhs, $rhs: $Rhs) { + $action + } + } + } +); + +/// Macro for the implementation of assignement-multiplication and assignement-division with and +/// without reference to the right-hand-side. +macro_rules! md_assign_impl_all( + ( + // Operator, operator method, and scalar bounds. + $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; + // Storage dimensions, and dimension bounds. + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + // [Optional] Extra allocator bounds. + $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; + // Argument identifiers and types. + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; + // Actual implementation and lifetimes. + [val] => $action_val: expr; + [ref] => $action_ref: expr;) => { + md_assign_impl!( + $Op, $op $(where N: $($ScalarBounds),*)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + $lhs: $Lhs, $rhs: $Rhs; + $action_val; ); + + md_assign_impl!( + $Op, $op $(where N: $($ScalarBounds),*)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + $lhs: $Lhs, $rhs: &'b $Rhs; + $action_ref; 'b); + } +); + +// FIXME: merge with `as_impl`. +/// Macro for the implementation of addition and subtraction. +macro_rules! add_sub_impl( + ($Op: ident, $op: ident, $bound: ident; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(-> ($RRes: ty))* for $($Dims: ident: $DimsBound: ident),+; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs + where N: Scalar + $bound, + SA: Storage, + SB: Storage, + SA::Alloc: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows<$R1, $R2 $(, Representative = $RRes)*> + + SameNumberOfColumns<$C1, $C2> { + type Output = $Result; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + +// FIXME: merge with `md_assign_impl`. +/// Macro for the implementation of assignement-addition and assignement-subtraction. +macro_rules! add_sub_assign_impl( + ($Op: ident, $op: ident, $bound: ident; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident),+; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs + where N: Scalar + $bound, + SA: OwnedStorage, // FIXME: this is too restrictive. + SB: Storage, + SA::Alloc: OwnedAllocator, + ShapeConstraint: SameNumberOfRows<$R1, $R2> + SameNumberOfColumns<$C1, $C2> { + #[inline] + fn $op(&mut $lhs, $rhs: $Rhs) { + $action + } + } + } +); diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs new file mode 100644 index 00000000..18ebd9ed --- /dev/null +++ b/src/geometry/orthographic.rs @@ -0,0 +1,269 @@ +#[cfg(feature="arbitrary")] +use quickcheck::{Arbitrary, Gen}; +use rand::{Rand, Rng}; + +use alga::general::Real; + +use core::{Scalar, SquareMatrix, OwnedSquareMatrix, ColumnVector, OwnedColumnVector, MatrixArray}; +use core::dimension::{U1, U3, U4}; +use core::storage::{OwnedStorage, Storage, StorageMut}; +use core::allocator::OwnedAllocator; +use core::helper; + +use geometry::{PointBase, OwnedPoint}; + +/// A 3D orthographic projection stored as an homogeneous 4x4 matrix. +#[derive(Debug, Clone, Copy)] // FIXME: Hash +pub struct OrthographicBase> { + matrix: SquareMatrix +} + +/// A 3D orthographic projection stored as a static homogeneous 4x4 matrix. +pub type Orthographic3 = OrthographicBase>; + +impl Eq for OrthographicBase + where N: Scalar + Eq, + S: Storage { } + +impl PartialEq for OrthographicBase + where N: Scalar, + S: Storage { + #[inline] + fn eq(&self, right: &Self) -> bool { + self.matrix == right.matrix + } +} + +impl OrthographicBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new orthographic projection matrix. + #[inline] + pub fn new(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> Self { + assert!(left < right, "The left corner must be farther than the right corner."); + assert!(bottom < top, "The top corner must be higher than the bottom corner."); + assert!(znear < zfar, "The far plane must be farther than the near plane."); + + let matrix = SquareMatrix::::identity(); + let mut res = Self::from_matrix_unchecked(matrix); + + res.set_left_and_right(left, right); + res.set_bottom_and_top(bottom, top); + res.set_znear_and_zfar(znear, zfar); + + res + } + + /// Wraps the given matrix to interpret it as a 3D orthographic matrix. + /// + /// It is not checked whether or not the given matrix actually represents an orthographic + /// projection. + #[inline] + pub fn from_matrix_unchecked(matrix: SquareMatrix) -> Self { + OrthographicBase { + matrix: matrix + } + } + + /// Creates a new orthographic projection matrix from an aspect ratio and the vertical field of view. + #[inline] + pub fn from_fov(aspect: N, vfov: N, znear: N, zfar: N) -> Self { + assert!(znear < zfar, "The far plane must be farther than the near plane."); + assert!(!relative_eq!(aspect, N::zero()), "The apsect ratio must not be zero."); + + let half: N = ::convert(0.5); + let width = zfar * (vfov * half).tan(); + let height = width / aspect; + + Self::new(-width * half, width * half, -height * half, height * half, znear, zfar) + } +} + +impl OrthographicBase + where N: Real, + S: Storage { + + /// A reference to the underlying homogeneous transformation matrix. + #[inline] + pub fn as_matrix(&self) -> &SquareMatrix { + &self.matrix + } + + /// Retrieves the underlying homogeneous matrix. + #[inline] + pub fn unwrap(self) -> SquareMatrix { + self.matrix + } + + /// Computes the corresponding homogeneous matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix { + self.matrix.clone_owned() + } + + /// The smallest x-coordinate of the view cuboid. + #[inline] + pub fn left(&self) -> N { + (-N::one() - self.matrix[(0, 3)]) / self.matrix[(0, 0)] + } + + /// The largest x-coordinate of the view cuboid. + #[inline] + pub fn right(&self) -> N { + (N::one() - self.matrix[(0, 3)]) / self.matrix[(0, 0)] + } + + /// The smallest y-coordinate of the view cuboid. + #[inline] + pub fn bottom(&self) -> N { + (-N::one() - self.matrix[(1, 3)]) / self.matrix[(1, 1)] + } + + /// The largest y-coordinate of the view cuboid. + #[inline] + pub fn top(&self) -> N { + (N::one() - self.matrix[(1, 3)]) / self.matrix[(1, 1)] + } + + /// The near plane offset of the view cuboid. + #[inline] + pub fn znear(&self) -> N { + (N::one() + self.matrix[(2, 3)]) / self.matrix[(2, 2)] + } + + /// The far plane offset of the view cuboid. + #[inline] + pub fn zfar(&self) -> N { + (-N::one() + self.matrix[(2, 3)]) / self.matrix[(2, 2)] + } + + // FIXME: when we get specialization, specialize the Mul impl instead. + /// Projects a point. Faster than matrix multiplication. + #[inline] + pub fn project_point(&self, p: &PointBase) -> OwnedPoint + where SB: Storage { + + OwnedPoint::::new( + self.matrix[(0, 0)] * p[0] + self.matrix[(0, 3)], + self.matrix[(1, 1)] * p[1] + self.matrix[(1, 3)], + self.matrix[(2, 2)] * p[2] + self.matrix[(2, 3)] + ) + } + + // FIXME: when we get specialization, specialize the Mul impl instead. + /// Projects a vector. Faster than matrix multiplication. + #[inline] + pub fn project_vector(&self, p: &ColumnVector) -> OwnedColumnVector + where SB: Storage { + + OwnedColumnVector::::new( + self.matrix[(0, 0)] * p[0], + self.matrix[(1, 1)] * p[1], + self.matrix[(2, 2)] * p[2] + ) + } +} + +impl OrthographicBase + where N: Real, + S: StorageMut { + /// Sets the smallest x-coordinate of the view cuboid. + #[inline] + pub fn set_left(&mut self, left: N) { + let right = self.right(); + self.set_left_and_right(left, right); + } + + /// Sets the largest x-coordinate of the view cuboid. + #[inline] + pub fn set_right(&mut self, right: N) { + let left = self.left(); + self.set_left_and_right(left, right); + } + + /// Sets the smallest y-coordinate of the view cuboid. + #[inline] + pub fn set_bottom(&mut self, bottom: N) { + let top = self.top(); + self.set_bottom_and_top(bottom, top); + } + + /// Sets the largest y-coordinate of the view cuboid. + #[inline] + pub fn set_top(&mut self, top: N) { + let bottom = self.bottom(); + self.set_bottom_and_top(bottom, top); + } + + /// Sets the near plane offset of the view cuboid. + #[inline] + pub fn set_znear(&mut self, znear: N) { + let zfar = self.zfar(); + self.set_znear_and_zfar(znear, zfar); + } + + /// Sets the far plane offset of the view cuboid. + #[inline] + pub fn set_zfar(&mut self, zfar: N) { + let znear = self.znear(); + self.set_znear_and_zfar(znear, zfar); + } + + /// Sets the view cuboid coordinates along the `x` axis. + #[inline] + pub fn set_left_and_right(&mut self, left: N, right: N) { + assert!(left < right, "The left corner must be farther than the right corner."); + self.matrix[(0, 0)] = ::convert::<_, N>(2.0) / (right - left); + self.matrix[(0, 3)] = -(right + left) / (right - left); + } + + /// Sets the view cuboid coordinates along the `y` axis. + #[inline] + pub fn set_bottom_and_top(&mut self, bottom: N, top: N) { + assert!(bottom < top, "The top corner must be higher than the bottom corner."); + self.matrix[(1, 1)] = ::convert::<_, N>(2.0) / (top - bottom); + self.matrix[(1, 3)] = -(top + bottom) / (top - bottom); + } + + /// Sets the near and far plane offsets of the view cuboid. + #[inline] + pub fn set_znear_and_zfar(&mut self, znear: N, zfar: N) { + assert!(!relative_eq!(zfar - znear, N::zero()), "The near-plane and far-plane must not be superimposed."); + self.matrix[(2, 2)] = -::convert::<_, N>(2.0) / (zfar - znear); + self.matrix[(2, 3)] = -(zfar + znear) / (zfar - znear); + } +} + +impl Rand for OrthographicBase + where N: Real + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + fn rand(r: &mut R) -> Self { + let left = Rand::rand(r); + let right = helper::reject_rand(r, |x: &N| *x > left); + let bottom = Rand::rand(r); + let top = helper::reject_rand(r, |x: &N| *x > bottom); + let znear = Rand::rand(r); + let zfar = helper::reject_rand(r, |x: &N| *x > znear); + + Self::new(left, right, bottom, top, znear, zfar) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for OrthographicBase + where N: Real + Arbitrary, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + fn arbitrary(g: &mut G) -> Self { + let left = Arbitrary::arbitrary(g); + let right = helper::reject(g, |x: &N| *x > left); + let bottom = Arbitrary::arbitrary(g); + let top = helper::reject(g, |x: &N| *x > bottom); + let znear = Arbitrary::arbitrary(g); + let zfar = helper::reject(g, |x: &N| *x > znear); + + Self::new(left, right, bottom, top, znear, zfar) + } +} diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs new file mode 100644 index 00000000..55bf59b5 --- /dev/null +++ b/src/geometry/perspective.rs @@ -0,0 +1,222 @@ +#[cfg(feature="arbitrary")] +use quickcheck::{Arbitrary, Gen}; +use rand::{Rand, Rng}; + +use alga::general::Real; + +use core::{Scalar, SquareMatrix, OwnedSquareMatrix, ColumnVector, OwnedColumnVector, MatrixArray}; +use core::dimension::{U1, U3, U4}; +use core::storage::{OwnedStorage, Storage, StorageMut}; +use core::allocator::OwnedAllocator; +use core::helper; + +use geometry::{PointBase, OwnedPoint}; + +/// A 3D perspective projection stored as an homogeneous 4x4 matrix. +#[derive(Debug, Clone, Copy)] // FIXME: Hash +pub struct PerspectiveBase> { + matrix: SquareMatrix +} + +/// A 3D perspective projection stored as a static homogeneous 4x4 matrix. +pub type Perspective3 = PerspectiveBase>; + +impl Eq for PerspectiveBase + where N: Scalar + Eq, + S: Storage { } + +impl PartialEq for PerspectiveBase + where N: Scalar, + S: Storage { + #[inline] + fn eq(&self, right: &Self) -> bool { + self.matrix == right.matrix + } +} + +impl PerspectiveBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new perspective matrix from the aspect ratio, y field of view, and near/far planes. + pub fn new(aspect: N, fovy: N, znear: N, zfar: N) -> Self { + assert!(!relative_eq!(zfar - znear, N::zero()), "The near-plane and far-plane must not be superimposed."); + assert!(!relative_eq!(aspect, N::zero()), "The apsect ratio must not be zero."); + + let matrix = SquareMatrix::::identity(); + let mut res = PerspectiveBase::from_matrix_unchecked(matrix); + + res.set_fovy(fovy); + res.set_aspect(aspect); + res.set_znear_and_zfar(znear, zfar); + + res.matrix[(3, 3)] = N::zero(); + res.matrix[(3, 2)] = -N::one(); + + res + } + + + /// Wraps the given matrix to interpret it as a 3D perspective matrix. + /// + /// It is not checked whether or not the given matrix actually represents an orthographic + /// projection. + #[inline] + pub fn from_matrix_unchecked(matrix: SquareMatrix) -> Self { + PerspectiveBase { + matrix: matrix + } + } +} + +impl PerspectiveBase + where N: Real, + S: Storage { + + /// A reference to the underlying homogeneous transformation matrix. + #[inline] + pub fn as_matrix(&self) -> &SquareMatrix { + &self.matrix + } + + /// Retrieves the underlying homogeneous matrix. + #[inline] + pub fn unwrap(self) -> SquareMatrix { + self.matrix + } + + /// Computes the corresponding homogeneous matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix { + self.matrix.clone_owned() + } + + /// Gets the `width / height` aspect ratio of the view frustrum. + #[inline] + pub fn aspect(&self) -> N { + self.matrix[(1, 1)] / self.matrix[(0, 0)] + } + + /// Gets the y field of view of the view frustrum. + #[inline] + pub fn fovy(&self) -> N { + (N::one() / self.matrix[(1, 1)]).atan() * ::convert(2.0) + } + + /// Gets the near plane offset of the view frustrum. + #[inline] + pub fn znear(&self) -> N { + let ratio = (-self.matrix[(2, 2)] + N::one()) / (-self.matrix[(2, 2)] - N::one()); + + self.matrix[(2, 3)] / (ratio * ::convert(2.0)) - self.matrix[(2, 3)] / ::convert(2.0) + } + + /// Gets the far plane offset of the view frustrum. + #[inline] + pub fn zfar(&self) -> N { + let ratio = (-self.matrix[(2, 2)] + N::one()) / (-self.matrix[(2, 2)] - N::one()); + + (self.matrix[(2, 3)] - ratio * self.matrix[(2, 3)]) / ::convert(2.0) + } + + // FIXME: add a method to retrieve znear and zfar simultaneously? + + + + // FIXME: when we get specialization, specialize the Mul impl instead. + /// Projects a point. Faster than matrix multiplication. + #[inline] + pub fn project_point(&self, p: &PointBase) -> OwnedPoint + where SB: Storage { + + let inverse_denom = -N::one() / p[2]; + OwnedPoint::::new( + self.matrix[(0, 0)] * p[0] * inverse_denom, + self.matrix[(1, 1)] * p[1] * inverse_denom, + (self.matrix[(2, 2)] * p[2] + self.matrix[(2, 3)]) * inverse_denom + ) + } + + // FIXME: when we get specialization, specialize the Mul impl instead. + /// Projects a vector. Faster than matrix multiplication. + #[inline] + pub fn project_vector(&self, p: &ColumnVector) -> OwnedColumnVector + where SB: Storage { + + let inverse_denom = -N::one() / p[2]; + OwnedColumnVector::::new( + self.matrix[(0, 0)] * p[0] * inverse_denom, + self.matrix[(1, 1)] * p[1] * inverse_denom, + self.matrix[(2, 2)] + ) + } +} + + +impl PerspectiveBase + where N: Real, + S: StorageMut { + /// Updates this perspective matrix with a new `width / height` aspect ratio of the view + /// frustrum. + #[inline] + pub fn set_aspect(&mut self, aspect: N) { + assert!(!relative_eq!(aspect, N::zero()), "The aspect ratio must not be zero."); + self.matrix[(0, 0)] = self.matrix[(1, 1)] / aspect; + } + + /// Updates this perspective with a new y field of view of the view frustrum. + #[inline] + pub fn set_fovy(&mut self, fovy: N) { + let old_m22 = self.matrix[(1, 1)]; + self.matrix[(1, 1)] = N::one() / (fovy / ::convert(2.0)).tan(); + self.matrix[(0, 0)] = self.matrix[(0, 0)] * (self.matrix[(1, 1)] / old_m22); + } + + /// Updates this perspective matrix with a new near plane offset of the view frustrum. + #[inline] + pub fn set_znear(&mut self, znear: N) { + let zfar = self.zfar(); + self.set_znear_and_zfar(znear, zfar); + } + + /// Updates this perspective matrix with a new far plane offset of the view frustrum. + #[inline] + pub fn set_zfar(&mut self, zfar: N) { + let znear = self.znear(); + self.set_znear_and_zfar(znear, zfar); + } + + /// Updates this perspective matrix with new near and far plane offsets of the view frustrum. + #[inline] + pub fn set_znear_and_zfar(&mut self, znear: N, zfar: N) { + self.matrix[(2, 2)] = (zfar + znear) / (znear - zfar); + self.matrix[(2, 3)] = zfar * znear * ::convert(2.0) / (znear - zfar); + } +} + +impl Rand for PerspectiveBase + where N: Real + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + fn rand(r: &mut R) -> Self { + let znear = Rand::rand(r); + let zfar = helper::reject_rand(r, |&x: &N| !(x - znear).is_zero()); + let aspect = helper::reject_rand(r, |&x: &N| !x.is_zero()); + + Self::new(aspect, Rand::rand(r), znear, zfar) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for PerspectiveBase + where N: Real + Arbitrary, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + fn arbitrary(g: &mut G) -> Self { + let znear = Arbitrary::arbitrary(g); + let zfar = helper::reject(g, |&x: &N| !(x - znear).is_zero()); + let aspect = helper::reject(g, |&x: &N| !x.is_zero()); + + Self::new(aspect, Arbitrary::arbitrary(g), znear, zfar) + } +} diff --git a/src/geometry/point.rs b/src/geometry/point.rs new file mode 100644 index 00000000..e29158ee --- /dev/null +++ b/src/geometry/point.rs @@ -0,0 +1,228 @@ +use num::One; +use std::fmt; +use std::cmp::Ordering; +use approx::ApproxEq; + +use core::{Scalar, ColumnVector, OwnedColumnVector}; +use core::iter::{MatrixIter, MatrixIterMut}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::{Storage, StorageMut, MulStorage}; +use core::allocator::{Allocator, SameShapeR}; + +pub type PointSumStorage = + <>::Alloc as Allocator, U1>>::Buffer; + +// XXX Bad name: we can't even add points… +/// The type of the result of the sum of a point with a vector. +pub type PointSum = + PointBase, PointSumStorage>; + +/// The type of the result of the multiplication of a point by a matrix. +pub type PointMul = PointBase>; + +/// A point with an owned storage. +pub type OwnedPoint = PointBase>::Buffer>; + +/// A point in n-dimensional euclidean space. +#[repr(C)] +#[derive(Hash, Debug)] +pub struct PointBase> { + pub coords: ColumnVector +} + +impl Copy for PointBase + where N: Scalar, + D: DimName, + S: Storage + Copy { } + +impl Clone for PointBase + where N: Scalar, + D: DimName, + S: Storage + Clone { + #[inline] + fn clone(&self) -> Self { + PointBase::from_coordinates(self.coords.clone()) + } +} + +impl> PointBase { + /// Creates a new point with the given coordinates. + #[inline] + pub fn from_coordinates(coords: ColumnVector) -> PointBase { + PointBase { + coords: coords + } + } +} + +impl> PointBase { + /// Moves this point into one that owns its data. + #[inline] + pub fn into_owned(self) -> OwnedPoint { + PointBase::from_coordinates(self.coords.into_owned()) + } + + /// Clones this point into one that owns its data. + #[inline] + pub fn clone_owned(&self) -> OwnedPoint { + PointBase::from_coordinates(self.coords.clone_owned()) + } + + /// The dimension of this point. + #[inline] + pub fn len(&self) -> usize { + self.coords.len() + } + + /// The stride of this point. This is the number of buffer element separating each component of + /// this point. + #[inline] + pub fn stride(&self) -> usize { + self.coords.strides().0 + } + + /// Iterates through this point coordinates. + #[inline] + pub fn iter(&self) -> MatrixIter { + self.coords.iter() + } + + /// Gets a reference to i-th element of this point without bound-checking. + #[inline] + pub unsafe fn get_unchecked(&self, i: usize) -> &N { + self.coords.get_unchecked(i, 0) + } + + + /// Converts this point into a vector in homogeneous coordinates, i.e., appends a `1` at the + /// end of it. + #[inline] + pub fn to_homogeneous(&self) -> OwnedColumnVector, S::Alloc> + where N: One, + D: DimNameAdd, + S::Alloc: Allocator, U1> { + + let mut res = unsafe { OwnedColumnVector::::new_uninitialized() }; + res.fixed_slice_mut::(0, 0).copy_from(&self.coords); + res[(D::dim(), 0)] = N::one(); + + res + } +} + +impl> PointBase { + /// Mutably iterates through this point coordinates. + #[inline] + pub fn iter_mut(&mut self) -> MatrixIterMut { + self.coords.iter_mut() + } + + /// Gets a mutable reference to i-th element of this point without bound-checking. + #[inline] + pub unsafe fn get_unchecked_mut(&mut self, i: usize) -> &mut N { + self.coords.get_unchecked_mut(i, 0) + } + + /// Swaps two entries without bound-checking. + #[inline] + pub unsafe fn swap_unchecked(&mut self, i1: usize, i2: usize) { + self.coords.swap_unchecked((i1, 0), (i2, 0)) + } +} + +impl ApproxEq for PointBase + where N: Scalar + ApproxEq, + S: Storage, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.coords.relative_eq(&other.coords, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.coords.ulps_eq(&other.coords, epsilon, max_ulps) + } +} + +impl Eq for PointBase + where N: Scalar + Eq, + S: Storage { } + +impl PartialEq for PointBase + where N: Scalar, + S: Storage { + #[inline] + fn eq(&self, right: &Self) -> bool { + self.coords == right.coords + } +} + +impl PartialOrd for PointBase + where N: Scalar + PartialOrd, + S: Storage { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.coords.partial_cmp(&other.coords) + } + + #[inline] + fn lt(&self, right: &Self) -> bool { + self.coords.lt(&right.coords) + } + + #[inline] + fn le(&self, right: &Self) -> bool { + self.coords.le(&right.coords) + } + + #[inline] + fn gt(&self, right: &Self) -> bool { + self.coords.gt(&right.coords) + } + + #[inline] + fn ge(&self, right: &Self) -> bool { + self.coords.ge(&right.coords) + } +} + +/* + * + * Display + * + */ +impl fmt::Display for PointBase + where N: Scalar + fmt::Display, + S: Storage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "{{")); + + let mut it = self.coords.iter(); + + try!(write!(f, "{}", *it.next().unwrap())); + + for comp in it { + try!(write!(f, ", {}", *comp)); + } + + write!(f, "}}") + } +} diff --git a/src/geometry/point_alga.rs b/src/geometry/point_alga.rs new file mode 100644 index 00000000..d237af64 --- /dev/null +++ b/src/geometry/point_alga.rs @@ -0,0 +1,83 @@ +use alga::general::{Field, Real, MeetSemilattice, JoinSemilattice, Lattice}; +use alga::linear::{AffineSpace, EuclideanSpace}; + +use core::{ColumnVector, Scalar}; +use core::dimension::{DimName, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::PointBase; + + +impl AffineSpace for PointBase + where N: Scalar + Field, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Translation = ColumnVector; +} + +impl EuclideanSpace for PointBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Coordinates = ColumnVector; + type Real = N; + + #[inline] + fn origin() -> Self { + Self::origin() + } + + #[inline] + fn coordinates(&self) -> Self::Coordinates { + self.coords.clone() + } + + #[inline] + fn from_coordinates(coords: Self::Coordinates) -> Self { + Self::from_coordinates(coords) + } + + #[inline] + fn scale_by(&self, n: N) -> Self { + self * n + } +} + +/* + * + * Ordering + * + */ +impl MeetSemilattice for PointBase + where N: Scalar + MeetSemilattice, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn meet(&self, other: &Self) -> Self { + PointBase::from_coordinates(self.coords.meet(&other.coords)) + } +} + +impl JoinSemilattice for PointBase + where N: Scalar + JoinSemilattice, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn join(&self, other: &Self) -> Self { + PointBase::from_coordinates(self.coords.join(&other.coords)) + } +} + + +impl Lattice for PointBase + where N: Scalar + Lattice, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn meet_join(&self, other: &Self) -> (Self, Self) { + let (meet, join) = self.coords.meet_join(&other.coords); + + (PointBase::from_coordinates(meet), PointBase::from_coordinates(join)) + } +} diff --git a/src/geometry/point_alias.rs b/src/geometry/point_alias.rs new file mode 100644 index 00000000..20cce1bc --- /dev/null +++ b/src/geometry/point_alias.rs @@ -0,0 +1,20 @@ +use core::MatrixArray; +use core::dimension::{U1, U2, U3, U4, U5, U6}; + +use geometry::PointBase; + +/// A statically sized D-dimensional column point. +pub type Point = PointBase>; + +/// A statically sized 1-dimensional column point. +pub type Point1 = Point; +/// A statically sized 2-dimensional column point. +pub type Point2 = Point; +/// A statically sized 3-dimensional column point. +pub type Point3 = Point; +/// A statically sized 4-dimensional column point. +pub type Point4 = Point; +/// A statically sized 5-dimensional column point. +pub type Point5 = Point; +/// A statically sized 6-dimensional column point. +pub type Point6 = Point; diff --git a/src/geometry/point_construction.rs b/src/geometry/point_construction.rs new file mode 100644 index 00000000..5a3d6e93 --- /dev/null +++ b/src/geometry/point_construction.rs @@ -0,0 +1,127 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use rand::{Rand, Rng}; +use num::{Zero, One, Bounded}; + +use alga::general::ClosedDiv; +use core::{Scalar, ColumnVector}; +use core::storage::{Storage, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1, U2, U3, U4, U5, U6}; + +use geometry::PointBase; + +impl PointBase + where N: Scalar, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new point with uninitialized coordinates. + #[inline] + pub unsafe fn new_uninitialized() -> Self { + Self::from_coordinates(ColumnVector::<_, D, _>::new_uninitialized()) + } + + /// Creates a new point with all coordinates equal to zero. + #[inline] + pub fn origin() -> Self + where N: Zero { + Self::from_coordinates(ColumnVector::<_, D, _>::from_element(N::zero())) + } + + /// Creates a new point from its homogeneous vector representation. + /// + /// In practice, this builds a D-dimensional points with the same first D component as `v` + /// divided by the last component of `v`. Returns `None` if this divisor is zero. + #[inline] + pub fn from_homogeneous(v: ColumnVector, SB>) -> Option + where N: Scalar + Zero + One + ClosedDiv, + D: DimNameAdd, + SB: Storage, U1, Alloc = S::Alloc>, + S::Alloc: Allocator, U1> { + + if !v[D::dim()].is_zero() { + let coords = v.fixed_slice::(0, 0) / v[D::dim()]; + Some(Self::from_coordinates(coords)) + } + else { + None + } + } +} + + +/* + * + * Traits that buid points. + * + */ +impl Bounded for PointBase + where N: Scalar + Bounded, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn max_value() -> Self { + Self::from_coordinates(ColumnVector::max_value()) + } + + #[inline] + fn min_value() -> Self { + Self::from_coordinates(ColumnVector::min_value()) + } +} + +impl Rand for PointBase + where N: Scalar + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut G) -> Self { + PointBase::from_coordinates(rng.gen()) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for PointBase + where N: Scalar + Arbitrary + Send, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(g: &mut G) -> Self { + PointBase::from_coordinates(ColumnVector::arbitrary(g)) + } +} + +/* + * + * Small points construction from components. + * + */ +macro_rules! componentwise_constructors_impl( + ($($D: ty, $($args: ident:$irow: expr),*);* $(;)*) => {$( + impl PointBase + where N: Scalar, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Initializes this matrix from its components. + #[inline] + pub fn new($($args: N),*) -> PointBase { + unsafe { + let mut res = Self::new_uninitialized(); + $( *res.get_unchecked_mut($irow) = $args; )* + + res + } + } + } + )*} +); + +componentwise_constructors_impl!( + U1, x:0; + U2, x:0, y:1; + U3, x:0, y:1, z:2; + U4, x:0, y:1, z:2, w:3; + U5, x:0, y:1, z:2, w:3, a:4; + U6, x:0, y:1, z:2, w:3, a:4, b:5; +); diff --git a/src/geometry/point_conversion.rs b/src/geometry/point_conversion.rs new file mode 100644 index 00000000..2b463c72 --- /dev/null +++ b/src/geometry/point_conversion.rs @@ -0,0 +1,73 @@ +use num::{One, Zero}; +use alga::general::{SubsetOf, SupersetOf, ClosedDiv}; + +use core::{Scalar, Matrix, ColumnVector, OwnedColumnVector}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; + +use geometry::{PointBase, OwnedPoint}; + +/* + * This file provides the following conversions: + * ============================================= + * + * PointBase -> PointBase + * PointBase -> ColumnVector (homogeneous) + */ + +impl SubsetOf> for PointBase + where D: DimName, + N1: Scalar, + N2: Scalar + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SB::Alloc: OwnedAllocator, + SA::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> PointBase { + PointBase::from_coordinates(self.coords.to_superset()) + } + + #[inline] + fn is_in_subset(m: &PointBase) -> bool { + // FIXME: is there a way to reuse the `.is_in_subset` from the matrix implementation of + // SubsetOf? + m.iter().all(|e| e.is_in_subset()) + } + + #[inline] + unsafe fn from_superset_unchecked(m: &PointBase) -> Self { + PointBase::from_coordinates(Matrix::from_superset_unchecked(&m.coords)) + } +} + + +impl SubsetOf, SB>> for PointBase + where D: DimNameAdd, + N1: Scalar, + N2: Scalar + Zero + One + ClosedDiv + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, U1>, + SA::Alloc: OwnedAllocator + + Allocator, U1>, + SB::Alloc: OwnedAllocator, U1, SB> + + Allocator { + #[inline] + fn to_superset(&self) -> ColumnVector, SB> { + let p: OwnedPoint = self.to_superset(); + p.to_homogeneous() + } + + #[inline] + fn is_in_subset(v: &ColumnVector, SB>) -> bool { + ::is_convertible::<_, OwnedColumnVector, SA::Alloc>>(v) && + !v[D::dim()].is_zero() + } + + #[inline] + unsafe fn from_superset_unchecked(v: &ColumnVector, SB>) -> Self { + let coords = v.fixed_slice::(0, 0) / v[D::dim()]; + Self::from_coordinates(::convert_unchecked(coords)) + } +} diff --git a/src/geometry/point_coordinates.rs b/src/geometry/point_coordinates.rs new file mode 100644 index 00000000..5eaf15cc --- /dev/null +++ b/src/geometry/point_coordinates.rs @@ -0,0 +1,47 @@ +use std::mem; +use std::ops::{Deref, DerefMut}; + +use core::Scalar; +use core::dimension::{U1, U2, U3, U4, U5, U6}; +use core::coordinates::{X, XY, XYZ, XYZW, XYZWA, XYZWAB}; +use core::allocator::OwnedAllocator; +use core::storage::OwnedStorage; + +use geometry::PointBase; + +/* + * + * Give coordinates to PointBase{1 .. 6} + * + */ + +macro_rules! deref_impl( + ($D: ty, $Target: ident $(, $comps: ident)*) => { + impl Deref for PointBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Target = $Target; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { mem::transmute(self) } + } + } + + impl DerefMut for PointBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { mem::transmute(self) } + } + } + } +); + +deref_impl!(U1, X, x); +deref_impl!(U2, XY, x, y); +deref_impl!(U3, XYZ, x, y, z); +deref_impl!(U4, XYZW, x, y, z, w); +deref_impl!(U5, XYZWA, x, y, z, w, a); +deref_impl!(U6, XYZWAB, x, y, z, w, a, b); diff --git a/src/geometry/point_ops.rs b/src/geometry/point_ops.rs new file mode 100644 index 00000000..4a9deb7f --- /dev/null +++ b/src/geometry/point_ops.rs @@ -0,0 +1,263 @@ +use std::ops::{Neg, Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Index, IndexMut}; +use num::Zero; + +use alga::general::{ClosedNeg, ClosedAdd, ClosedSub, ClosedMul, ClosedDiv}; + +use core::{Scalar, ColumnVector, Matrix, ColumnVectorSum}; +use core::dimension::{Dim, DimName, U1}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, AreMultipliable}; +use core::storage::{Storage, StorageMut}; +use core::allocator::{SameShapeAllocator, Allocator}; + +use geometry::{PointBase, OwnedPoint, PointMul}; + + +/* + * + * Indexing. + * + */ +impl Index for PointBase + where N: Scalar, + S: Storage { + type Output = N; + + #[inline] + fn index(&self, i: usize) -> &Self::Output { + &self.coords[i] + } +} + +impl IndexMut for PointBase + where N: Scalar, + S: StorageMut { + #[inline] + fn index_mut(&mut self, i: usize) -> &mut Self::Output { + &mut self.coords[i] + } +} + +/* + * Neg. + * + */ +impl Neg for PointBase + where N: Scalar + ClosedNeg, + S: Storage { + type Output = OwnedPoint; + + #[inline] + fn neg(self) -> Self::Output { + PointBase::from_coordinates(-self.coords) + } +} + +impl<'a, N, D: DimName, S> Neg for &'a PointBase + where N: Scalar + ClosedNeg, + S: Storage { + type Output = OwnedPoint; + + #[inline] + fn neg(self) -> Self::Output { + PointBase::from_coordinates(-&self.coords) + } +} + +/* + * + * Subtraction & Addition. + * + */ + +// PointBase - PointBase +add_sub_impl!(Sub, sub, ClosedSub; + (D, U1), (D, U1) for D: DimName; + self: &'a PointBase, right: &'b PointBase, Output = ColumnVectorSum; + &self.coords - &right.coords; 'a, 'b); + +add_sub_impl!(Sub, sub, ClosedSub; + (D, U1), (D, U1) for D: DimName; + self: &'a PointBase, right: PointBase, Output = ColumnVectorSum; + &self.coords - right.coords; 'a); + +add_sub_impl!(Sub, sub, ClosedSub; + (D, U1), (D, U1) for D: DimName; + self: PointBase, right: &'b PointBase, Output = ColumnVectorSum; + self.coords - &right.coords; 'b); + +add_sub_impl!(Sub, sub, ClosedSub; + (D, U1), (D, U1) for D: DimName; + self: PointBase, right: PointBase, Output = ColumnVectorSum; + self.coords - right.coords; ); + +// PointBase - Vector +add_sub_impl!(Sub, sub, ClosedSub; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: &'a PointBase, right: &'b ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(&self.coords - right); 'a, 'b); + +add_sub_impl!(Sub, sub, ClosedSub; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: &'a PointBase, right: ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(&self.coords - &right); 'a); // FIXME: should not be a ref to `right`. + +add_sub_impl!(Sub, sub, ClosedSub; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: PointBase, right: &'b ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(self.coords - right); 'b); + +add_sub_impl!(Sub, sub, ClosedSub; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: PointBase, right: ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(self.coords - right); ); + + +// PointBase + Vector +add_sub_impl!(Add, add, ClosedAdd; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: &'a PointBase, right: &'b ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(&self.coords + right); 'a, 'b); + +add_sub_impl!(Add, add, ClosedAdd; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: &'a PointBase, right: ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(&self.coords + &right); 'a); // FIXME: should not be a ref to `right`. + +add_sub_impl!(Add, add, ClosedAdd; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: PointBase, right: &'b ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(self.coords + right); 'b); + +add_sub_impl!(Add, add, ClosedAdd; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; + self: PointBase, right: ColumnVector, Output = OwnedPoint; + Self::Output::from_coordinates(self.coords + right); ); + + +// XXX: replace by the shared macro: add_sub_assign_impl +macro_rules! op_assign_impl( + ($($TraitAssign: ident, $method_assign: ident, $bound: ident);* $(;)*) => {$( + impl<'b, N, D1: DimName, D2: Dim, SA, SB> $TraitAssign<&'b ColumnVector> for PointBase + where N: Scalar + $bound, + SA: StorageMut, + SB: Storage, + ShapeConstraint: SameNumberOfRows { + + #[inline] + fn $method_assign(&mut self, right: &'b ColumnVector) { + self.coords.$method_assign(right) + } + } + + impl $TraitAssign> for PointBase + where N: Scalar + $bound, + SA: StorageMut, + SB: Storage, + ShapeConstraint: SameNumberOfRows { + + #[inline] + fn $method_assign(&mut self, right: ColumnVector) { + self.coords.$method_assign(right) + } + } + )*} +); + +op_assign_impl!( + AddAssign, add_assign, ClosedAdd; + SubAssign, sub_assign, ClosedSub; +); + + +/* + * + * Matrix × PointBase + * + */ +md_impl_all!( + Mul, mul; + (R1, C1), (D2, U1) for R1: DimName, C1: Dim, D2: DimName + where SA::Alloc: Allocator + where ShapeConstraint: AreMultipliable; + self: Matrix, right: PointBase, Output = PointMul; + [val val] => PointBase::from_coordinates(self * right.coords); + [ref val] => PointBase::from_coordinates(self * right.coords); + [val ref] => PointBase::from_coordinates(self * &right.coords); + [ref ref] => PointBase::from_coordinates(self * &right.coords); +); + + + +/* + * + * PointBase ×/÷ Scalar + * + */ +macro_rules! componentwise_scalarop_impl( + ($Trait: ident, $method: ident, $bound: ident; + $TraitAssign: ident, $method_assign: ident) => { + impl $Trait for PointBase + where N: Scalar + $bound, + S: Storage { + type Output = OwnedPoint; + + #[inline] + fn $method(self, right: N) -> Self::Output { + PointBase::from_coordinates(self.coords.$method(right)) + } + } + + impl<'a, N, D: DimName, S> $Trait for &'a PointBase + where N: Scalar + $bound, + S: Storage { + type Output = OwnedPoint; + + #[inline] + fn $method(self, right: N) -> Self::Output { + PointBase::from_coordinates((&self.coords).$method(right)) + } + } + + impl $TraitAssign for PointBase + where N: Scalar + $bound, + S: StorageMut { + #[inline] + fn $method_assign(&mut self, right: N) { + self.coords.$method_assign(right) + } + } + } +); + +componentwise_scalarop_impl!(Mul, mul, ClosedMul; MulAssign, mul_assign); +componentwise_scalarop_impl!(Div, div, ClosedDiv; DivAssign, div_assign); + +macro_rules! left_scalar_mul_impl( + ($($T: ty),* $(,)*) => {$( + impl Mul> for $T + where S: Storage<$T, D, U1> { + type Output = OwnedPoint<$T, D, S::Alloc>; + + #[inline] + fn mul(self, right: PointBase<$T, D, S>) -> Self::Output { + PointBase::from_coordinates(self * right.coords) + } + } + + impl<'b, D: DimName, S> Mul<&'b PointBase<$T, D, S>> for $T + where S: Storage<$T, D, U1> { + type Output = OwnedPoint<$T, D, S::Alloc>; + + #[inline] + fn mul(self, right: &'b PointBase<$T, D, S>) -> Self::Output { + PointBase::from_coordinates(self * &right.coords) + } + } + )*} +); + +left_scalar_mul_impl!( + u8, u16, u32, u64, usize, + i8, i16, i32, i64, isize, + f32, f64 +); diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs new file mode 100644 index 00000000..9332dfa2 --- /dev/null +++ b/src/geometry/quaternion.rs @@ -0,0 +1,455 @@ +use std::fmt; +use num::Zero; +use approx::ApproxEq; + +use alga::general::Real; + +use core::{Unit, ColumnVector, OwnedColumnVector, MatrixSlice, MatrixSliceMut, SquareMatrix, + OwnedSquareMatrix}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; +use core::dimension::{U1, U3, U4}; + +use geometry::{RotationBase, OwnedRotation}; + +/// A quaternion with an owned storage allocated by `A`. +pub type OwnedQuaternionBase = QuaternionBase>::Buffer>; + +/// A unit quaternion with an owned storage allocated by `A`. +pub type OwnedUnitQuaternionBase = UnitQuaternionBase>::Buffer>; + +/// A quaternion. See the type alias `UnitQuaternionBase = Unit` for a quaternion +/// that may be used as a rotation. +#[repr(C)] +#[derive(Hash, Debug, Copy, Clone)] +pub struct QuaternionBase> { + pub coords: ColumnVector +} + +impl Eq for QuaternionBase + where N: Real + Eq, + S: Storage { +} + +impl PartialEq for QuaternionBase + where N: Real, + S: Storage { + fn eq(&self, rhs: &Self) -> bool { + self.coords == rhs.coords || + // Account for the double-covering of S², i.e. q = -q + self.as_vector().iter().zip(rhs.as_vector().iter()).all(|(a, b)| *a == -*b) + } +} + +impl QuaternionBase + where N: Real, + S: Storage { + /// Moves this quaternion into one that owns its data. + #[inline] + pub fn into_owned(self) -> OwnedQuaternionBase { + QuaternionBase::from_vector(self.coords.into_owned()) + } + + /// Clones this quaternion into one that owns its data. + #[inline] + pub fn clone_owned(&self) -> OwnedQuaternionBase { + QuaternionBase::from_vector(self.coords.clone_owned()) + } + + /// The vector part `(i, j, k)` of this quaternion. + #[inline] + pub fn vector(&self) -> MatrixSlice { + self.coords.fixed_rows::(0) + } + + /// The scalar part `w` of this quaternion. + #[inline] + pub fn scalar(&self) -> N { + self.coords[3] + } + + /// Reinterprets this quaternion as a 4D vector. + #[inline] + pub fn as_vector(&self) -> &ColumnVector { + &self.coords + } + + /// The norm of this quaternion. + #[inline] + pub fn norm(&self) -> N { + self.coords.norm() + } + + /// The squared norm of this quaternion. + #[inline] + pub fn norm_squared(&self) -> N { + self.coords.norm_squared() + } + + /// Compute the conjugate of this quaternion. + #[inline] + pub fn conjugate(&self) -> OwnedQuaternionBase { + let v = OwnedColumnVector::::new(-self.coords[0], + -self.coords[1], + -self.coords[2], + self.coords[3]); + QuaternionBase::from_vector(v) + } + + /// Inverts this quaternion if it is not zero. + #[inline] + pub fn try_inverse(&self) -> Option> { + let mut res = QuaternionBase::from_vector(self.coords.clone_owned()); + + if res.try_inverse_mut() { + Some(res) + } + else { + None + } + } +} + + +impl QuaternionBase + where N: Real, + S: Storage, + S::Alloc: Allocator { + /// The polar decomposition of this quaternion. + /// + /// Returns, from left to right: the quaternion norm, the half rotation angle, the rotation + /// axis. If the rotation angle is zero, the rotation axis is set to `None`. + pub fn polar_decomposition(&self) -> (N, N, Option>>) { + if let Some((q, n)) = Unit::try_new_and_get(self.clone_owned(), N::zero()) { + if let Some(axis) = Unit::try_new(self.vector().clone_owned(), N::zero()) { + let angle = q.angle() / ::convert(2.0f64); + + (n, angle, Some(axis)) + } + else { + (n, N::zero(), None) + } + } + else { + (N::zero(), N::zero(), None) + } + } + + /// Compute the exponential of a quaternion. + #[inline] + pub fn exp(&self) -> OwnedQuaternionBase { + let v = self.vector(); + let nn = v.norm_squared(); + + if relative_eq!(nn, N::zero()) { + QuaternionBase::identity() + } + else { + let w_exp = self.scalar().exp(); + let n = nn.sqrt(); + let nv = v * (w_exp * n.sin() / n); + + QuaternionBase::from_parts(n.cos(), nv) + } + } + + /// Compute the natural logarithm of a quaternion. + #[inline] + pub fn ln(&self) -> OwnedQuaternionBase { + let n = self.norm(); + let v = self.vector(); + let s = self.scalar(); + + QuaternionBase::from_parts(n.ln(), v.normalize() * (s / n).acos()) + } + + /// Raise the quaternion to a given floating power. + #[inline] + pub fn powf(&self, n: N) -> OwnedQuaternionBase { + (self.ln() * n).exp() + } +} + +impl QuaternionBase + where N: Real, + S: StorageMut { + /// Transforms this quaternion into its 4D vector form (Vector part, Scalar part). + #[inline] + pub fn as_vector_mut(&mut self) -> &mut ColumnVector { + &mut self.coords + } + + /// The mutable vector part `(i, j, k)` of this quaternion. + #[inline] + pub fn vector_mut(&mut self) -> MatrixSliceMut { + self.coords.fixed_rows_mut::(0) + } + + /// Replaces this quaternion by its conjugate. + #[inline] + pub fn conjugate_mut(&mut self) { + self.coords[0] = -self.coords[0]; + self.coords[1] = -self.coords[1]; + self.coords[2] = -self.coords[2]; + } + + /// Inverts this quaternion in-place if it is not zero. + #[inline] + pub fn try_inverse_mut(&mut self) -> bool { + let norm_squared = self.norm_squared(); + + if relative_eq!(&norm_squared, &N::zero()) { + false + } + else { + self.conjugate_mut(); + self.coords /= norm_squared; + + true + } + } +} + +impl ApproxEq for QuaternionBase + where N: Real + ApproxEq, + S: Storage { + type Epsilon = N; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.as_vector().relative_eq(other.as_vector(), epsilon, max_relative) || + // Account for the double-covering of S², i.e. q = -q + self.as_vector().iter().zip(other.as_vector().iter()).all(|(a, b)| a.relative_eq(&-*b, epsilon, max_relative)) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.as_vector().ulps_eq(other.as_vector(), epsilon, max_ulps) || + // Account for the double-covering of S², i.e. q = -q. + self.as_vector().iter().zip(other.as_vector().iter()).all(|(a, b)| a.ulps_eq(&-*b, epsilon, max_ulps)) + } +} + + +impl fmt::Display for QuaternionBase + where N: Real + fmt::Display, + S: Storage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Quaternion {} − ({}, {}, {})", self[3], self[0], self[1], self[2]) + } +} + +/// A unit quaternions. May be used to represent a rotation. +pub type UnitQuaternionBase = Unit>; + + + +impl UnitQuaternionBase + where N: Real, + S: Storage { + /// The rotation angle in [0; pi] of this unit quaternion. + #[inline] + pub fn angle(&self) -> N { + let w = self.quaternion().scalar().abs(); + + // Handle innacuracies that make break `.acos`. + if w >= N::one() { + N::zero() + } + else { + w.acos() * ::convert(2.0f64) + } + } + + /// The underlying quaternion. + /// + /// Same as `self.as_ref()`. + #[inline] + pub fn quaternion(&self) -> &QuaternionBase { + self.as_ref() + } + + /// Compute the conjugate of this unit quaternion. + #[inline] + pub fn conjugate(&self) -> OwnedUnitQuaternionBase { + UnitQuaternionBase::new_unchecked(self.as_ref().conjugate()) + } + + /// Inverts this quaternion if it is not zero. + #[inline] + pub fn inverse(&self) -> OwnedUnitQuaternionBase { + self.conjugate() + } + + /// The rotation angle needed to make `self` and `other` coincide. + #[inline] + pub fn angle_to(&self, other: &UnitQuaternionBase) -> N + where S2: Storage { + let delta = self.rotation_to(other); + delta.angle() + } + + /// The unit quaternion needed to make `self` and `other` coincide. + /// + /// The result is such that: `self.rotation_to(other) * self == other`. + #[inline] + pub fn rotation_to(&self, other: &UnitQuaternionBase) -> OwnedUnitQuaternionBase + where S2: Storage { + other / self + } +} + +impl UnitQuaternionBase + where N: Real, + S: StorageMut { + /// Compute the conjugate of this unit quaternion in-place. + #[inline] + pub fn conjugate_mut(&mut self) { + self.as_mut_unchecked().conjugate_mut() + } + + /// Inverts this quaternion if it is not zero. + #[inline] + pub fn inverse_mut(&mut self) { + self.as_mut_unchecked().conjugate_mut() + } +} + +impl UnitQuaternionBase + where N: Real, + S: Storage, + S::Alloc: Allocator { + /// The rotation axis of this unit quaternion or `None` if the rotation is zero. + #[inline] + pub fn axis(&self) -> Option>> { + let v = + if self.quaternion().scalar() >= N::zero() { + self.as_ref().vector().clone_owned() + } + else { + -self.as_ref().vector() + }; + + Unit::try_new(v, N::zero()) + } + + + /// The rotation axis of this unit quaternion multiplied by the rotation agle. + #[inline] + pub fn scaled_axis(&self) -> OwnedColumnVector { + if let Some(axis) = self.axis() { + axis.unwrap() * self.angle() + } + else { + ColumnVector::zero() + } + } + + /// Compute the exponential of a quaternion. + /// + /// Note that this function yields a `QuaternionBase` because it looses the unit property. + #[inline] + pub fn exp(&self) -> OwnedQuaternionBase { + self.as_ref().exp() + } + + /// Compute the natural logarithm of a quaternion. + /// + /// Note that this function yields a `QuaternionBase` because it looses the unit property. + /// The vector part of the return value corresponds to the axis-angle representation (divided + /// by 2.0) of this unit quaternion. + #[inline] + pub fn ln(&self) -> OwnedQuaternionBase { + if let Some(v) = self.axis() { + QuaternionBase::from_parts(N::zero(), v.unwrap() * self.angle()) + } + else { + QuaternionBase::zero() + } + } + + /// Raise the quaternion to a given floating power. + /// + /// This returns the unit quaternion that identifies a rotation with axis `self.axis()` and + /// angle `self.angle() × n`. + #[inline] + pub fn powf(&self, n: N) -> OwnedUnitQuaternionBase { + if let Some(v) = self.axis() { + UnitQuaternionBase::from_axis_angle(&v, self.angle() * n) + } + else { + UnitQuaternionBase::identity() + } + } +} + +impl UnitQuaternionBase + where N: Real, + S: Storage, + S::Alloc: Allocator { + /// Builds a rotation matrix from this unit quaternion. + #[inline] + pub fn to_rotation_matrix(&self) -> OwnedRotation { + let i = self.as_ref()[0]; + let j = self.as_ref()[1]; + let k = self.as_ref()[2]; + let w = self.as_ref()[3]; + + let ww = w * w; + let ii = i * i; + let jj = j * j; + let kk = k * k; + let ij = i * j * ::convert(2.0f64); + let wk = w * k * ::convert(2.0f64); + let wj = w * j * ::convert(2.0f64); + let ik = i * k * ::convert(2.0f64); + let jk = j * k * ::convert(2.0f64); + let wi = w * i * ::convert(2.0f64); + + RotationBase::from_matrix_unchecked( + SquareMatrix::<_, U3, _>::new( + ww + ii - jj - kk, ij - wk, wj + ik, + wk + ij, ww - ii + jj - kk, jk - wi, + ik - wj, wi + jk, ww - ii - jj + kk + ) + ) + } + + /// Converts this unit quaternion into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix + where S::Alloc: Allocator { + self.to_rotation_matrix().to_homogeneous() + } +} + + +impl fmt::Display for UnitQuaternionBase + where N: Real + fmt::Display, + S: Storage, + S::Alloc: Allocator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(axis) = self.axis() { + let axis = axis.unwrap(); + write!(f, "UnitQuaternion angle: {} − axis: ({}, {}, {})", self.angle(), axis[0], axis[1], axis[2]) + } + else { + write!(f, "UnitQuaternion angle: {} − axis: (undefined)", self.angle()) + } + } +} diff --git a/src/geometry/quaternion_alga.rs b/src/geometry/quaternion_alga.rs new file mode 100644 index 00000000..16cd03f3 --- /dev/null +++ b/src/geometry/quaternion_alga.rs @@ -0,0 +1,382 @@ +use num::Zero; + +use alga::general::{AbstractMagma, AbstractGroup, AbstractGroupAbelian, AbstractLoop, + AbstractMonoid, AbstractQuasigroup, AbstractSemigroup, AbstractModule, + Module, Real, Inverse, Multiplicative, Additive, Identity, Id}; +use alga::linear::{Transformation, AffineTransformation, Similarity, Isometry, DirectIsometry, + OrthogonalTransformation, VectorSpace, FiniteDimVectorSpace, NormedSpace, + Rotation, ProjectiveTransformation}; + +use core::ColumnVector; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; +use core::dimension::{U1, U3, U4}; +use geometry::{PointBase, QuaternionBase, UnitQuaternionBase}; + + +impl Identity for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl Identity for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::zero() + } +} + +impl AbstractMagma for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +impl AbstractMagma for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self + rhs + } +} + +impl Inverse for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Self { + -self + } +} + +macro_rules! impl_structures( + ($Quaternion: ident; $($marker: ident<$operator: ident>),* $(,)*) => {$( + impl $marker<$operator> for $Quaternion + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +impl_structures!( + QuaternionBase; + AbstractSemigroup, + AbstractMonoid, + + AbstractSemigroup, + AbstractQuasigroup, + AbstractMonoid, + AbstractLoop, + AbstractGroup, + AbstractGroupAbelian +); + + +/* + * + * Vector space. + * + */ +impl AbstractModule for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type AbstractRing = N; + + #[inline] + fn multiply_by(&self, n: N) -> Self { + self * n + } +} + +impl Module for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Ring = N; +} + +impl VectorSpace for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Field = N; +} + +impl FiniteDimVectorSpace for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn dimension() -> usize { + 4 + } + + #[inline] + fn canonical_basis_element(i: usize) -> Self { + Self::from_vector(ColumnVector::canonical_basis_element(i)) + } + + #[inline] + fn dot(&self, other: &Self) -> N { + self.coords.dot(&other.coords) + } + + #[inline] + unsafe fn component_unchecked(&self, i: usize) -> &N { + self.coords.component_unchecked(i) + } + + #[inline] + unsafe fn component_unchecked_mut(&mut self, i: usize) -> &mut N { + self.coords.component_unchecked_mut(i) + } +} + +impl NormedSpace for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn norm_squared(&self) -> N { + self.coords.norm_squared() + } + + #[inline] + fn norm(&self) -> N { + self.as_vector().norm() + } + + #[inline] + fn normalize(&self) -> Self { + let v = self.coords.normalize(); + Self::from_vector(v) + } + + #[inline] + fn normalize_mut(&mut self) -> N { + self.coords.normalize_mut() + } + + #[inline] + fn try_normalize(&self, min_norm: N) -> Option { + if let Some(v) = self.coords.try_normalize(min_norm) { + Some(Self::from_vector(v)) + } + else { + None + } + } + + #[inline] + fn try_normalize_mut(&mut self, min_norm: N) -> Option { + self.coords.try_normalize_mut(min_norm) + } +} + +/* + * + * Implementations for UnitQuaternionBase. + * + */ +impl Identity for UnitQuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl AbstractMagma for UnitQuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +impl Inverse for UnitQuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Self { + self.inverse() + } + + #[inline] + fn inverse_mut(&mut self) { + self.inverse_mut() + } +} + +impl_structures!( + UnitQuaternionBase; + AbstractSemigroup, + AbstractQuasigroup, + AbstractMonoid, + AbstractLoop, + AbstractGroup +); + +impl Transformation> for UnitQuaternionBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + self * pt + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self * v + } +} + +impl ProjectiveTransformation> for UnitQuaternionBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + // FIXME: would it be useful performancewise not to call inverse explicitly (i-e. implement + // the inverse transformation explicitly here) ? + self.inverse() * pt + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self.inverse() * v + } +} + +impl AffineTransformation> for UnitQuaternionBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + type Rotation = Self; + type NonUniformScaling = Id; + type Translation = Id; + + #[inline] + fn decompose(&self) -> (Id, Self, Id, Self) { + (Id::new(), self.clone(), Id::new(), Self::identity()) + } + + #[inline] + fn append_translation(&self, _: &Self::Translation) -> Self { + self.clone() + } + + #[inline] + fn prepend_translation(&self, _: &Self::Translation) -> Self { + self.clone() + } + + #[inline] + fn append_rotation(&self, r: &Self::Rotation) -> Self { + r * self + } + + #[inline] + fn prepend_rotation(&self, r: &Self::Rotation) -> Self { + self * r + } + + #[inline] + fn append_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } + + #[inline] + fn prepend_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } +} + +impl Similarity> for UnitQuaternionBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + type Scaling = Id; + + #[inline] + fn translation(&self) -> Id { + Id::new() + } + + #[inline] + fn rotation(&self) -> Self { + self.clone() + } + + #[inline] + fn scaling(&self) -> Id { + Id::new() + } +} + +macro_rules! marker_impl( + ($($Trait: ident),*) => {$( + impl $Trait> for UnitQuaternionBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { } + )*} +); + +marker_impl!(Isometry, DirectIsometry, OrthogonalTransformation); + + + +impl Rotation> for UnitQuaternionBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator + Allocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn powf(&self, n: N) -> Option { + Some(self.powf(n)) + } + + #[inline] + fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option { + Self::rotation_between(a, b) + } + + #[inline] + fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Option { + Self::scaled_rotation_between(a, b, s) + } +} diff --git a/src/geometry/quaternion_alias.rs b/src/geometry/quaternion_alias.rs new file mode 100644 index 00000000..36427e92 --- /dev/null +++ b/src/geometry/quaternion_alias.rs @@ -0,0 +1,10 @@ +use core::MatrixArray; +use core::dimension::{U1, U4}; + +use geometry::{QuaternionBase, UnitQuaternionBase}; + +/// A statically-allocated quaternion. +pub type Quaternion = QuaternionBase>; + +/// A statically-allocated unit quaternion. +pub type UnitQuaternion = UnitQuaternionBase>; diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs new file mode 100644 index 00000000..c83372be --- /dev/null +++ b/src/geometry/quaternion_construction.rs @@ -0,0 +1,344 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use rand::{Rand, Rng}; +use num::{Zero, One}; + +use alga::general::Real; + +use core::{Unit, ColumnVector, Vector3}; +use core::storage::{Storage, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; +use core::dimension::{U1, U3, U4}; + +use geometry::{QuaternionBase, UnitQuaternionBase, RotationBase, OwnedRotation}; + +impl QuaternionBase + where N: Real, + S: Storage { + /// Creates a quaternion from a 4D vector. The quaternion scalar part corresponds to the `w` + /// vector component. + #[inline] + pub fn from_vector(vector: ColumnVector) -> Self { + QuaternionBase { + coords: vector + } + } +} + +impl QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new quaternion from its individual components. Note that the arguments order does + /// **not** follow the storage order. + /// + /// The storage order is [ x, y, z, w ]. + #[inline] + pub fn new(w: N, x: N, y: N, z: N) -> Self { + let v = ColumnVector::::new(x, y, z, w); + Self::from_vector(v) + } + + /// Creates a new quaternion from its scalar and vector parts. Note that the arguments order does + /// **not** follow the storage order. + /// + /// The storage order is [ vector, scalar ]. + #[inline] + // FIXME: take a reference to `vector`? + pub fn from_parts(scalar: N, vector: ColumnVector) -> Self + where SB: Storage { + + Self::new(scalar, vector[0], vector[1], vector[2]) + } + + /// Creates a new quaternion from its polar decomposition. + /// + /// Note that `axis` is assumed to be a unit vector. + // FIXME: take a reference to `axis`? + pub fn from_polar_decomposition(scale: N, theta: N, axis: Unit>) -> Self + where SB: Storage { + let rot = UnitQuaternionBase::::from_axis_angle(&axis, theta * ::convert(2.0f64)); + + rot.unwrap() * scale + } + + /// The quaternion multiplicative identity. + #[inline] + pub fn identity() -> Self { + Self::new(N::one(), N::zero(), N::zero(), N::zero()) + } +} + +impl One for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Zero for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn zero() -> Self { + Self::new(N::zero(), N::zero(), N::zero(), N::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.coords.is_zero() + } +} + +impl Rand for QuaternionBase + where N: Real + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut R) -> Self { + QuaternionBase::new(rng.gen(), rng.gen(), rng.gen(), rng.gen()) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for QuaternionBase + where N: Real + Arbitrary, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(g: &mut G) -> Self { + QuaternionBase::new(N::arbitrary(g), N::arbitrary(g), + N::arbitrary(g), N::arbitrary(g)) + } +} + +impl UnitQuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// The quaternion multiplicative identity. + #[inline] + pub fn identity() -> Self { + Self::new_unchecked(QuaternionBase::identity()) + } + + /// Creates a new quaternion from a unit vector (the rotation axis) and an angle + /// (the rotation angle). + #[inline] + pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self + where SB: Storage { + let (sang, cang) = (angle / ::convert(2.0f64)).sin_cos(); + + let q = QuaternionBase::from_parts(cang, axis.as_ref() * sang); + Self::new_unchecked(q) + } + + /// Creates a new unit quaternion from a quaternion. + /// + /// The input quaternion will be normalized. + #[inline] + pub fn from_quaternion(q: QuaternionBase) -> Self { + Self::new_normalize(q) + } + + /// Creates a new unit quaternion from Euler angles. + /// + /// The primitive rotations are applied in order: 1 roll − 2 pitch − 3 yaw. + #[inline] + pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> Self { + let (sr, cr) = (roll * ::convert(0.5f64)).sin_cos(); + let (sp, cp) = (pitch * ::convert(0.5f64)).sin_cos(); + let (sy, cy) = (yaw * ::convert(0.5f64)).sin_cos(); + + let q = QuaternionBase::new( + cr * cp * cy + sr * sp * sy, + sr * cp * cy - cr * sp * sy, + cr * sp * cy + sr * cp * sy, + cr * cp * sy - sr * sp * cy); + + Self::new_unchecked(q) + } + + /// Builds an unit quaternion from a rotation matrix. + #[inline] + pub fn from_rotation_matrix(rotmat: &RotationBase) -> Self + where SB: Storage, + SB::Alloc: Allocator { + let angle = rotmat.angle(); + + if let Some(axis) = rotmat.axis() { + Self::from_axis_angle(&axis, angle) + } + else if angle > ::convert(1.0f64) { + // The angle is 3.14. + -Self::identity() + } + else { + // The angle is 0. + Self::identity() + } + } + + /// The unit quaternion needed to make `a` and `b` be collinear and point toward the same + /// direction. + #[inline] + pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option + where SB: Storage, + SC: Storage { + Self::scaled_rotation_between(a, b, N::one()) + } + + /// The smallest rotation needed to make `a` and `b` collinear and point toward the same + /// direction, raised to the power `s`. + #[inline] + pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Option + where SB: Storage, + SC: Storage { + // FIXME: code duplication with RotationBase. + if let (Some(na), Some(nb)) = (a.try_normalize(N::zero()), b.try_normalize(N::zero())) { + let c = na.cross(&nb); + + if let Some(axis) = Unit::try_new(c, N::default_epsilon()) { + return Some(Self::from_axis_angle(&axis, na.dot(&nb).acos() * s)) + } + + // Zero or PI. + 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; + } + } + + Some(Self::identity()) + } + + + /// Creates an unit quaternion that corresponds to the local frame of an observer standing at the + /// origin and looking toward `dir`. + /// + /// It maps the view direction `dir` to the positive `z` axis. + /// + /// # Arguments + /// * dir - The look direction, that is, direction the matrix `z` axis will be aligned with. + /// * up - The vertical direction. The only requirement of this parameter is to not be + /// collinear + /// to `dir`. Non-collinearity is not checked. + #[inline] + pub fn new_observer_frame(dir: &ColumnVector, up: &ColumnVector) -> Self + where SB: Storage, + SC: Storage, + S::Alloc: Allocator + + Allocator { + Self::from_rotation_matrix(&OwnedRotation::::new_observer_frame(dir, up)) + } + + + /// Builds a right-handed look-at view matrix without translation. + /// + /// This conforms to the common notion of right handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_rh(dir: &ColumnVector, up: &ColumnVector) -> Self + where SB: Storage, + SC: Storage, + S::Alloc: Allocator + + Allocator { + Self::new_observer_frame(&-dir, up).inverse() + } + + /// Builds a left-handed look-at view matrix without translation. + /// + /// This conforms to the common notion of left handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_lh(dir: &ColumnVector, up: &ColumnVector) -> Self + where SB: Storage, + SC: Storage, + S::Alloc: Allocator + + Allocator { + Self::new_observer_frame(dir, up).inverse() + } +} + +impl UnitQuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator + + Allocator { + /// Creates a new unit quaternion rotation from a rotation axis scaled by the rotation angle. + /// + /// If `axisangle` is zero, this returns the indentity rotation. + #[inline] + pub fn new(axisangle: ColumnVector) -> Self + where SB: Storage { + let two: N = ::convert(2.0f64); + let q = QuaternionBase::::from_parts(N::zero(), axisangle / two).exp(); + Self::new_unchecked(q) + } + + /// Creates a new unit quaternion rotation from a rotation axis scaled by the rotation angle. + /// + /// If `axisangle` is zero, this returns the indentity rotation. + /// Same as `Self::new(axisangle)`. + #[inline] + pub fn from_scaled_axis(axisangle: ColumnVector) -> Self + where SB: Storage { + Self::new(axisangle) + } +} + +impl One for UnitQuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Rand for UnitQuaternionBase + where N: Real + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator + + Allocator { + #[inline] + fn rand(rng: &mut R) -> Self { + let axisangle = Vector3::rand(rng); + UnitQuaternionBase::from_scaled_axis(axisangle) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for UnitQuaternionBase + where N: Real + Arbitrary, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator + + Allocator { + #[inline] + fn arbitrary(g: &mut G) -> Self { + let axisangle = Vector3::arbitrary(g); + UnitQuaternionBase::from_scaled_axis(axisangle) + + } +} diff --git a/src/geometry/quaternion_conversion.rs b/src/geometry/quaternion_conversion.rs new file mode 100644 index 00000000..da70cda0 --- /dev/null +++ b/src/geometry/quaternion_conversion.rs @@ -0,0 +1,212 @@ +use num::Zero; + +use alga::general::{SubsetOf, SupersetOf, Real}; +use alga::linear::Rotation as AlgaRotation; + +use core::{ColumnVector, SquareMatrix}; +use core::dimension::{U1, U3, U4}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; +use geometry::{PointBase, QuaternionBase, UnitQuaternionBase, OwnedUnitQuaternionBase, RotationBase, + OwnedRotation, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine, TranslationBase}; + +/* + * This file provides the following conversions: + * ============================================= + * + * Quaternion -> Quaternion + * UnitQuaternion -> UnitQuaternion + * UnitQuaternion -> RotationBase + * UnitQuaternion -> IsometryBase + * UnitQuaternion -> SimilarityBase + * UnitQuaternion -> TransformBase + * UnitQuaternion -> Matrix (homogeneous) + * + * NOTE: + * UnitQuaternion -> Quaternion is already provided by: Unit -> T + */ + +impl SubsetOf> for QuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> QuaternionBase { + QuaternionBase::from_vector(self.coords.to_superset()) + } + + #[inline] + fn is_in_subset(q: &QuaternionBase) -> bool { + ::is_convertible::<_, ColumnVector>(&q.coords) + } + + #[inline] + unsafe fn from_superset_unchecked(q: &QuaternionBase) -> Self { + Self::from_vector(q.coords.to_subset_unchecked()) + } +} + +impl SubsetOf> for UnitQuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> UnitQuaternionBase { + UnitQuaternionBase::new_unchecked(self.as_ref().to_superset()) + } + + #[inline] + fn is_in_subset(uq: &UnitQuaternionBase) -> bool { + ::is_convertible::<_, QuaternionBase>(uq.as_ref()) + } + + #[inline] + unsafe fn from_superset_unchecked(uq: &UnitQuaternionBase) -> Self { + Self::new_unchecked(::convert_ref_unchecked(uq.as_ref())) + } +} + +impl SubsetOf> for UnitQuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator + + Allocator, + SB::Alloc: OwnedAllocator + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> RotationBase { + let q: OwnedUnitQuaternionBase = self.to_superset(); + q.to_rotation_matrix() + } + + #[inline] + fn is_in_subset(rot: &RotationBase) -> bool { + ::is_convertible::<_, OwnedRotation>(rot) + } + + #[inline] + unsafe fn from_superset_unchecked(rot: &RotationBase) -> Self { + let q = OwnedUnitQuaternionBase::::from_rotation_matrix(rot); + ::convert_unchecked(q) + } +} + + +impl SubsetOf> for UnitQuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + R: AlgaRotation> + SupersetOf>, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> IsometryBase { + IsometryBase::from_parts(TranslationBase::identity(), ::convert_ref(self)) + } + + #[inline] + fn is_in_subset(iso: &IsometryBase) -> bool { + iso.translation.vector.is_zero() + } + + #[inline] + unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + ::convert_ref_unchecked(&iso.rotation) + } +} + + +impl SubsetOf> for UnitQuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + R: AlgaRotation> + SupersetOf>, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> SimilarityBase { + SimilarityBase::from_isometry(::convert_ref(self), N2::one()) + } + + #[inline] + fn is_in_subset(sim: &SimilarityBase) -> bool { + sim.isometry.translation.vector.is_zero() && + sim.scaling() == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + ::convert_ref_unchecked(&sim.isometry) + } +} + + +impl SubsetOf> for UnitQuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + C: SuperTCategoryOf, + SA::Alloc: OwnedAllocator + + Allocator + + Allocator + + Allocator, + SB::Alloc: OwnedAllocator + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf> for UnitQuaternionBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator + + Allocator + + Allocator + + Allocator, + SB::Alloc: OwnedAllocator + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> SquareMatrix { + self.to_homogeneous().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix) -> bool { + ::is_convertible::<_, OwnedRotation>(m) + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix) -> Self { + let rot: OwnedRotation = ::convert_ref_unchecked(m); + Self::from_rotation_matrix(&rot) + } +} diff --git a/src/geometry/quaternion_coordinates.rs b/src/geometry/quaternion_coordinates.rs new file mode 100644 index 00000000..aeb5beca --- /dev/null +++ b/src/geometry/quaternion_coordinates.rs @@ -0,0 +1,34 @@ +use std::ops::{Deref, DerefMut}; +use std::mem; + +use alga::general::Real; + +use core::coordinates::IJKW; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; +use core::dimension::{U1, U4}; + +use geometry::QuaternionBase; + + +impl Deref for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Target = IJKW; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { mem::transmute(self) } + } +} + +impl DerefMut for QuaternionBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { mem::transmute(self) } + } +} diff --git a/src/geometry/quaternion_ops.rs b/src/geometry/quaternion_ops.rs new file mode 100644 index 00000000..ce577176 --- /dev/null +++ b/src/geometry/quaternion_ops.rs @@ -0,0 +1,729 @@ +/* + * This file provides: + * =================== + * + * + * (Quaternion) + * + * Index + * IndexMut + * Quaternion × Quaternion + * Quaternion + Quaternion + * Quaternion - Quaternion + * -Quaternion + * Quaternion × Scalar + * Quaternion ÷ Scalar + * Scalar × Quaternion + * + * (Unit Quaternion) + * UnitQuaternion × UnitQuaternion + * UnitQuaternion × RotationBase -> UnitQuaternion + * RotationBase × UnitQuaternion -> UnitQuaternion + * + * UnitQuaternion ÷ UnitQuaternion + * UnitQuaternion ÷ RotationBase -> UnitQuaternion + * RotationBase ÷ UnitQuaternion -> UnitQuaternion + * + * + * UnitQuaternion × PointBase + * UnitQuaternion × ColumnVector + * UnitQuaternion × Unit + * + * NOTE: -UnitQuaternion is already provided by `Unit`. + * + * + * (Assignment Operators) + * + * Quaternion ×= Scalar + * Quaternion ×= Quaternion + * Quaternion += Quaternion + * Quaternion -= Quaternion + * + * UnitQuaternion ×= UnitQuaternion + * UnitQuaternion ×= RotationBase + * + * UnitQuaternion ÷= UnitQuaternion + * UnitQuaternion ÷= RotationBase + * + * FIXME: RotationBase ×= UnitQuaternion + * FIXME: RotationBase ÷= UnitQuaternion + * + */ + +use std::ops::{Index, IndexMut, Neg, Add, AddAssign, Mul, MulAssign, Div, DivAssign, Sub, SubAssign}; + +use alga::general::Real; + +use core::{ColumnVector, OwnedColumnVector, Unit}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; +use core::dimension::{U1, U3, U4}; + +use geometry::{QuaternionBase, OwnedQuaternionBase, UnitQuaternionBase, OwnedUnitQuaternionBase, + PointBase, OwnedPoint, RotationBase}; + +impl Index for QuaternionBase + where N: Real, + S: Storage { + type Output = N; + + #[inline] + fn index(&self, i: usize) -> &N { + &self.coords[i] + } +} + +impl IndexMut for QuaternionBase + where N: Real, + S: StorageMut { + + #[inline] + fn index_mut(&mut self, i: usize) -> &mut N { + &mut self.coords[i] + } +} + +macro_rules! quaternion_op_impl( + ($Op: ident, $op: ident; + ($LhsRDim: ident, $LhsCDim: ident), ($RhsRDim: ident, $RhsCDim: ident); + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty $(=> $VDimA: ty, $VDimB: ty)*; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N, SA, SB> $Op<$Rhs> for $Lhs + where N: Real, + SA: Storage, + SB: Storage, + $(SA::Alloc: Allocator, + SB::Alloc: Allocator)* { + type Output = $Result; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + + +// Quaternion + Quaternion +quaternion_op_impl!( + Add, add; + (U4, U1), (U4, U1); + self: &'a QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(&self.coords + &rhs.coords); + 'a, 'b); + +quaternion_op_impl!( + Add, add; + (U4, U1), (U4, U1); + self: &'a QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(&self.coords + rhs.coords); + 'a); + +quaternion_op_impl!( + Add, add; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(self.coords + &rhs.coords); + 'b); + +quaternion_op_impl!( + Add, add; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(self.coords + rhs.coords); + ); + + +// Quaternion - Quaternion +quaternion_op_impl!( + Sub, sub; + (U4, U1), (U4, U1); + self: &'a QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(&self.coords - &rhs.coords); + 'a, 'b); + +quaternion_op_impl!( + Sub, sub; + (U4, U1), (U4, U1); + self: &'a QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(&self.coords - rhs.coords); + 'a); + +quaternion_op_impl!( + Sub, sub; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(self.coords - &rhs.coords); + 'b); + +quaternion_op_impl!( + Sub, sub; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::from_vector(self.coords - rhs.coords); + ); + + +// Quaternion × Quaternion +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: &'a QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + QuaternionBase::new( + self[3] * rhs[3] - self[0] * rhs[0] - self[1] * rhs[1] - self[2] * rhs[2], + self[3] * rhs[0] + self[0] * rhs[3] + self[1] * rhs[2] - self[2] * rhs[1], + self[3] * rhs[1] - self[0] * rhs[2] + self[1] * rhs[3] + self[2] * rhs[0], + self[3] * rhs[2] + self[0] * rhs[1] - self[1] * rhs[0] + self[2] * rhs[3]); + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: &'a QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + self * &rhs; + 'a); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + &self * rhs; + 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + &self * &rhs; + ); + +// UnitQuaternion × UnitQuaternion +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: &'a UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + UnitQuaternionBase::new_unchecked(self.quaternion() * rhs.quaternion()); + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: &'a UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self * &rhs; + 'a); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + &self * rhs; + 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + &self * &rhs; + ); + +// UnitQuaternion ÷ UnitQuaternion +quaternion_op_impl!( + Div, div; + (U4, U1), (U4, U1); + self: &'a UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self * rhs.inverse(); + 'a, 'b); + +quaternion_op_impl!( + Div, div; + (U4, U1), (U4, U1); + self: &'a UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self / &rhs; + 'a); + +quaternion_op_impl!( + Div, div; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + &self / rhs; + 'b); + +quaternion_op_impl!( + Div, div; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + &self / &rhs; + ); + +// UnitQuaternion × RotationBase +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U3); + self: &'a UnitQuaternionBase, rhs: &'b RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + // FIXME: can we avoid the conversion from a rotation matrix? + self * OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U3); + self: &'a UnitQuaternionBase, rhs: RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + self * OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + 'a); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: &'b RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + self * OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + self * OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + ); + +// UnitQuaternion ÷ RotationBase +quaternion_op_impl!( + Div, div; + (U4, U1), (U3, U3); + self: &'a UnitQuaternionBase, rhs: &'b RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + // FIXME: can we avoid the conversion to a rotation matrix? + self / OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + 'a, 'b); + +quaternion_op_impl!( + Div, div; + (U4, U1), (U3, U3); + self: &'a UnitQuaternionBase, rhs: RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + self / OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + 'a); + +quaternion_op_impl!( + Div, div; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: &'b RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + self / OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + 'b); + +quaternion_op_impl!( + Div, div; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: RotationBase, + Output = OwnedUnitQuaternionBase => U3, U3; + self / OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + ); + +// RotationBase × UnitQuaternion +quaternion_op_impl!( + Mul, mul; + (U3, U3), (U4, U1); + self: &'a RotationBase, rhs: &'b UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + // FIXME: can we avoid the conversion from a rotation matrix? + OwnedUnitQuaternionBase::::from_rotation_matrix(self) * rhs; + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U3, U3), (U4, U1); + self: &'a RotationBase, rhs: UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + OwnedUnitQuaternionBase::::from_rotation_matrix(self) * rhs; + 'a); + +quaternion_op_impl!( + Mul, mul; + (U3, U3), (U4, U1); + self: RotationBase, rhs: &'b UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + OwnedUnitQuaternionBase::::from_rotation_matrix(&self) * rhs; + 'b); + +quaternion_op_impl!( + Mul, mul; + (U3, U3), (U4, U1); + self: RotationBase, rhs: UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + OwnedUnitQuaternionBase::::from_rotation_matrix(&self) * rhs; + ); + +// RotationBase ÷ UnitQuaternion +quaternion_op_impl!( + Div, div; + (U3, U3), (U4, U1); + self: &'a RotationBase, rhs: &'b UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + // FIXME: can we avoid the conversion from a rotation matrix? + OwnedUnitQuaternionBase::::from_rotation_matrix(self) / rhs; + 'a, 'b); + +quaternion_op_impl!( + Div, div; + (U3, U3), (U4, U1); + self: &'a RotationBase, rhs: UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + OwnedUnitQuaternionBase::::from_rotation_matrix(self) / rhs; + 'a); + +quaternion_op_impl!( + Div, div; + (U3, U3), (U4, U1); + self: RotationBase, rhs: &'b UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + OwnedUnitQuaternionBase::::from_rotation_matrix(&self) / rhs; + 'b); + +quaternion_op_impl!( + Div, div; + (U3, U3), (U4, U1); + self: RotationBase, rhs: UnitQuaternionBase, + Output = OwnedUnitQuaternionBase => U3, U3; + OwnedUnitQuaternionBase::::from_rotation_matrix(&self) / rhs; + ); + +// UnitQuaternion × Vector +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: &'a UnitQuaternionBase, rhs: &'b ColumnVector, + Output = OwnedColumnVector => U3, U4; + { + let two: N = ::convert(2.0f64); + let t = self.as_ref().vector().cross(rhs) * two; + let cross = self.as_ref().vector().cross(&t); + + t * self.as_ref().scalar() + cross + rhs + }; + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: &'a UnitQuaternionBase, rhs: ColumnVector, + Output = OwnedColumnVector => U3, U4; + self * &rhs; + 'a); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, rhs: &'b ColumnVector, + Output = OwnedColumnVector => U3, U4; + &self * rhs; + 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, rhs: ColumnVector, + Output = OwnedColumnVector => U3, U4; + &self * &rhs; + ); + +// UnitQuaternion × PointBase +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: &'a UnitQuaternionBase, rhs: &'b PointBase, + Output = OwnedPoint => U3, U4; + PointBase::from_coordinates(self * &rhs.coords); + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: &'a UnitQuaternionBase, rhs: PointBase, + Output = OwnedPoint => U3, U4; + PointBase::from_coordinates(self * rhs.coords); + 'a); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, rhs: &'b PointBase, + Output = OwnedPoint => U3, U4; + PointBase::from_coordinates(self * &rhs.coords); + 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, rhs: PointBase, + Output = OwnedPoint => U3, U4; + PointBase::from_coordinates(self * rhs.coords); + ); + +// UnitQuaternion × Unit +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: &'a UnitQuaternionBase, rhs: &'b Unit>, + Output = Unit> => U3, U4; + Unit::new_unchecked(self * rhs.as_ref()); + 'a, 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: &'a UnitQuaternionBase, rhs: Unit>, + Output = Unit> => U3, U4; + Unit::new_unchecked(self * rhs.unwrap()); + 'a); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, rhs: &'b Unit>, + Output = Unit> => U3, U4; + Unit::new_unchecked(self * rhs.as_ref()); + 'b); + +quaternion_op_impl!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, rhs: Unit>, + Output = Unit> => U3, U4; + Unit::new_unchecked(self * rhs.unwrap()); + ); + + + +macro_rules! scalar_op_impl( + ($($Op: ident, $op: ident, $OpAssign: ident, $op_assign: ident);* $(;)*) => {$( + impl $Op for QuaternionBase + where N: Real, + S: Storage { + type Output = OwnedQuaternionBase; + + #[inline] + fn $op(self, n: N) -> Self::Output { + QuaternionBase::from_vector(self.coords.$op(n)) + } + } + + impl<'a, N, S> $Op for &'a QuaternionBase + where N: Real, + S: Storage { + type Output = OwnedQuaternionBase; + + #[inline] + fn $op(self, n: N) -> Self::Output { + QuaternionBase::from_vector((&self.coords).$op(n)) + } + } + + impl $OpAssign for QuaternionBase + where N: Real, + S: StorageMut { + + #[inline] + fn $op_assign(&mut self, n: N) { + self.coords.$op_assign(n) + } + } + )*} +); + +scalar_op_impl!( + Mul, mul, MulAssign, mul_assign; + Div, div, DivAssign, div_assign; +); + +macro_rules! left_scalar_mul_impl( + ($($T: ty),* $(,)*) => {$( + impl Mul> for $T + where S: Storage<$T, U4, U1> { + type Output = OwnedQuaternionBase<$T, S::Alloc>; + + #[inline] + fn mul(self, right: QuaternionBase<$T, S>) -> Self::Output { + QuaternionBase::from_vector(self * right.coords) + } + } + + impl<'b, S> Mul<&'b QuaternionBase<$T, S>> for $T + where S: Storage<$T, U4, U1> { + type Output = OwnedQuaternionBase<$T, S::Alloc>; + + #[inline] + fn mul(self, right: &'b QuaternionBase<$T, S>) -> Self::Output { + QuaternionBase::from_vector(self * &right.coords) + } + } + )*} +); + +left_scalar_mul_impl!(f32, f64); + +impl Neg for QuaternionBase + where N: Real, + S: Storage { + type Output = OwnedQuaternionBase; + + #[inline] + fn neg(self) -> Self::Output { + QuaternionBase::from_vector(-self.coords) + } +} + +impl<'a, N, S> Neg for &'a QuaternionBase + where N: Real, + S: Storage { + type Output = OwnedQuaternionBase; + + #[inline] + fn neg(self) -> Self::Output { + QuaternionBase::from_vector(-&self.coords) + } +} + +macro_rules! quaternion_op_impl( + ($OpAssign: ident, $op_assign: ident; + ($LhsRDim: ident, $LhsCDim: ident), ($RhsRDim: ident, $RhsCDim: ident); + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty $(=> $VDimA: ty, $VDimB: ty)*; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N, SA, SB> $OpAssign<$Rhs> for $Lhs + where N: Real, + SA: StorageMut, + SB: Storage, + $(SA::Alloc: Allocator + Allocator, + // ^^^^^^^^^^^^^^^^^^^^ + // XXX: For some reasons, the compiler needs + // this bound to compile UnitQuat *= RotationBase. + // Though in theory this bound is already + // inherited from `SA: StorageMut`… + SB::Alloc: Allocator)* { + + #[inline] + fn $op_assign(&mut $lhs, $rhs: $Rhs) { + $action + } + } + } +); + +// Quaternion += Quaternion +quaternion_op_impl!( + AddAssign, add_assign; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: &'b QuaternionBase; + self.coords += &rhs.coords; + 'b); + +quaternion_op_impl!( + AddAssign, add_assign; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: QuaternionBase; + self.coords += rhs.coords; ); + + +// Quaternion -= Quaternion +quaternion_op_impl!( + SubAssign, sub_assign; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: &'b QuaternionBase; + self.coords -= &rhs.coords; + 'b); + +quaternion_op_impl!( + SubAssign, sub_assign; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: QuaternionBase; + self.coords -= rhs.coords; ); + +// Quaternion ×= Quaternion +quaternion_op_impl!( + MulAssign, mul_assign; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: &'b QuaternionBase; + { + let res = &*self * rhs; + // FIXME: will this be optimized away? + self.coords.copy_from(&res.coords); + }; + 'b); + +quaternion_op_impl!( + MulAssign, mul_assign; + (U4, U1), (U4, U1); + self: QuaternionBase, rhs: QuaternionBase; + *self *= &rhs; ); + +// UnitQuaternion ×= UnitQuaternion +quaternion_op_impl!( + MulAssign, mul_assign; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase; + { + let res = &*self * rhs; + self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); + }; + 'b); + +quaternion_op_impl!( + MulAssign, mul_assign; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: UnitQuaternionBase; + *self *= &rhs; ); + +// UnitQuaternion ÷= UnitQuaternion +quaternion_op_impl!( + DivAssign, div_assign; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase; + { + let res = &*self / rhs; + self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); + }; + 'b); + +quaternion_op_impl!( + DivAssign, div_assign; + (U4, U1), (U4, U1); + self: UnitQuaternionBase, rhs: UnitQuaternionBase; + *self /= &rhs; ); + +// UnitQuaternion ×= RotationBase +quaternion_op_impl!( + MulAssign, mul_assign; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: &'b RotationBase => U3, U3; + { + let res = &*self * rhs; + self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); + }; + 'b); + +quaternion_op_impl!( + MulAssign, mul_assign; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: RotationBase => U3, U3; + *self *= &rhs; ); + +// UnitQuaternion ÷= RotationBase +quaternion_op_impl!( + DivAssign, div_assign; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: &'b RotationBase => U3, U3; + { + let res = &*self / rhs; + self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); + }; + 'b); + +quaternion_op_impl!( + DivAssign, div_assign; + (U4, U1), (U3, U3); + self: UnitQuaternionBase, rhs: RotationBase => U3, U3; + *self /= &rhs; ); diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs new file mode 100644 index 00000000..651ade5d --- /dev/null +++ b/src/geometry/rotation.rs @@ -0,0 +1,173 @@ +use num::{Zero, One}; +use std::fmt; +use approx::ApproxEq; + +use alga::general::Real; + +use core::{SquareMatrix, Scalar, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; + + +/// A rotation matrix with an owned storage. +pub type OwnedRotation = RotationBase>::Buffer>; + +/// A rotation matrix. +#[repr(C)] +#[derive(Hash, Debug, Clone, Copy)] +pub struct RotationBase> { + matrix: SquareMatrix +} + +impl> RotationBase + where N: Scalar, + S: Storage { + /// A reference to the underlying matrix representation of this rotation. + #[inline] + pub fn matrix(&self) -> &SquareMatrix { + &self.matrix + } + + /// A mutable reference to the underlying matrix representation of this rotation. + /// + /// This is unsafe because this allows the user to replace the matrix by another one that is + /// non-square, non-inversible, or non-orthonormal. If one of those properties is broken, + /// subsequent method calls may be UB. + #[inline] + pub unsafe fn matrix_mut(&mut self) -> &mut SquareMatrix { + &mut self.matrix + } + + /// Unwraps the underlying matrix. + #[inline] + pub fn unwrap(self) -> SquareMatrix { + self.matrix + } + + /// Converts this rotation into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + where N: Zero + One, + D: DimNameAdd, + S::Alloc: Allocator, DimNameSum> { + let mut res = OwnedSquareMatrix::::identity(); + res.fixed_slice_mut::(0, 0).copy_from(&self.matrix); + + res + } +} + +impl> RotationBase { + /// Creates a new rotation from the given square matrix. + /// + /// The matrix squareness is checked but not its orthonormality. + #[inline] + pub fn from_matrix_unchecked(matrix: SquareMatrix) -> RotationBase { + assert!(matrix.is_square(), "Unable to create a rotation from a non-square matrix."); + + RotationBase { + matrix: matrix + } + } + + /// Transposes `self`. + #[inline] + pub fn transpose(&self) -> OwnedRotation { + RotationBase::from_matrix_unchecked(self.matrix.transpose()) + } + + /// Inverts `self`. + #[inline] + pub fn inverse(&self) -> OwnedRotation { + self.transpose() + } +} + + +impl> RotationBase { + /// Transposes `self` in-place. + #[inline] + pub fn transpose_mut(&mut self) { + self.matrix.transpose_mut() + } + + /// Inverts `self` in-place. + #[inline] + pub fn inverse_mut(&mut self) { + self.transpose_mut() + } +} + +impl> Eq for RotationBase { } + +impl> PartialEq for RotationBase { + #[inline] + fn eq(&self, right: &RotationBase) -> bool { + self.matrix == right.matrix + } +} + +impl ApproxEq for RotationBase + where N: Scalar + ApproxEq, + S: Storage, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.matrix.relative_eq(&other.matrix, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps) + } +} + +/* + * + * Display + * + */ +impl fmt::Display for RotationBase + where N: Real + fmt::Display, + S: Storage, + S::Alloc: Allocator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let precision = f.precision().unwrap_or(3); + + try!(writeln!(f, "RotationBase matrix {{")); + try!(write!(f, "{:.*}", precision, self.matrix)); + writeln!(f, "}}") + } +} + +// // /* +// // * +// // * Absolute +// // * +// // */ +// // impl Absolute for $t { +// // type AbsoluteValue = $submatrix; +// // +// // #[inline] +// // fn abs(m: &$t) -> $submatrix { +// // Absolute::abs(&m.submatrix) +// // } +// // } diff --git a/src/geometry/rotation_alga.rs b/src/geometry/rotation_alga.rs new file mode 100644 index 00000000..e53c53f3 --- /dev/null +++ b/src/geometry/rotation_alga.rs @@ -0,0 +1,298 @@ +use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, + AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; +use alga::linear::{Transformation, Similarity, AffineTransformation, Isometry, DirectIsometry, + OrthogonalTransformation, ProjectiveTransformation, Rotation}; + +use core::ColumnVector; +use core::dimension::{DimName, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{RotationBase, PointBase}; + + + +/* + * + * Algebraic structures. + * + */ +impl Identity for RotationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl Inverse for RotationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Self { + self.transpose() + } + + #[inline] + fn inverse_mut(&mut self) { + self.transpose_mut() + } +} + +impl AbstractMagma for RotationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +macro_rules! impl_multiplicative_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl $marker<$operator> for RotationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +impl_multiplicative_structures!( + AbstractSemigroup, + AbstractMonoid, + AbstractQuasigroup, + AbstractLoop, + AbstractGroup +); + +/* + * + * Transformation groups. + * + */ +impl Transformation> for RotationBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + self * pt + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self * v + } +} + +impl ProjectiveTransformation> for RotationBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + PointBase::from_coordinates(self.inverse_transform_vector(&pt.coords)) + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self.matrix().tr_mul(v) + } +} + +impl AffineTransformation> for RotationBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + type Rotation = Self; + type NonUniformScaling = Id; + type Translation = Id; + + #[inline] + fn decompose(&self) -> (Id, Self, Id, Self) { + (Id::new(), self.clone(), Id::new(), Self::identity()) + } + + #[inline] + fn append_translation(&self, _: &Self::Translation) -> Self { + self.clone() + } + + #[inline] + fn prepend_translation(&self, _: &Self::Translation) -> Self { + self.clone() + } + + #[inline] + fn append_rotation(&self, r: &Self::Rotation) -> Self { + r * self + } + + #[inline] + fn prepend_rotation(&self, r: &Self::Rotation) -> Self { + self * r + } + + #[inline] + fn append_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } + + #[inline] + fn prepend_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } +} + + +impl Similarity> for RotationBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + type Scaling = Id; + + #[inline] + fn translation(&self) -> Id { + Id::new() + } + + #[inline] + fn rotation(&self) -> Self { + self.clone() + } + + #[inline] + fn scaling(&self) -> Id { + Id::new() + } +} + +macro_rules! marker_impl( + ($($Trait: ident),*) => {$( + impl $Trait> for RotationBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { } + )*} +); + +marker_impl!(Isometry, DirectIsometry, OrthogonalTransformation); + + +/// Subgroups of the n-dimensional rotation group `SO(n)`. +impl Rotation> for RotationBase + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn powf(&self, n: N) -> Option { + // XXX: add the general case. + unimplemented!() + } + + #[inline] + fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option { + // XXX: add the general case. + unimplemented!() + } + + #[inline] + fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, n: N) -> Option { + // XXX: add the general case. + unimplemented!() + } +} + +/* +impl Matrix for RotationBase { + type Field = N; + type Row = Matrix; + type Column = Matrix; + type Transpose = Self; + + #[inline] + fn nrows(&self) -> usize { + self.submatrix.nrows() + } + + #[inline] + fn ncolumns(&self) -> usize { + self.submatrix.ncolumns() + } + + #[inline] + fn row(&self, i: usize) -> Self::Row { + self.submatrix.row(i) + } + + #[inline] + fn column(&self, i: usize) -> Self::Column { + self.submatrix.column(i) + } + + #[inline] + fn get(&self, i: usize, j: usize) -> Self::Field { + self.submatrix[(i, j)] + } + + #[inline] + unsafe fn get_unchecked(&self, i: usize, j: usize) -> Self::Field { + self.submatrix.at_fast(i, j) + } + + #[inline] + fn transpose(&self) -> Self::Transpose { + RotationBase::from_matrix_unchecked(self.submatrix.transpose()) + } +} + +impl SquareMatrix for RotationBase { + type Vector = Matrix; + + #[inline] + fn diagonal(&self) -> Self::Coordinates { + self.submatrix.diagonal() + } + + #[inline] + fn determinant(&self) -> Self::Field { + ::one() + } + + #[inline] + fn try_inverse(&self) -> Option { + Some(::transpose(self)) + } + + #[inline] + fn try_inverse_mut(&mut self) -> bool { + self.transpose_mut(); + true + } + + #[inline] + fn transpose_mut(&mut self) { + self.submatrix.transpose_mut() + } +} + +impl InversibleSquareMatrix for RotationBase { } +*/ + + diff --git a/src/geometry/rotation_alias.rs b/src/geometry/rotation_alias.rs new file mode 100644 index 00000000..50523aa7 --- /dev/null +++ b/src/geometry/rotation_alias.rs @@ -0,0 +1,13 @@ +use core::MatrixArray; +use core::dimension::{U2, U3}; + +use geometry::RotationBase; + +/// A D-dimensional rotation matrix. +pub type Rotation = RotationBase>; + +/// A 2-dimensional rotation matrix. +pub type Rotation2 = Rotation; + +/// A 3-dimensional rotation matrix. +pub type Rotation3 = Rotation; diff --git a/src/geometry/rotation_construction.rs b/src/geometry/rotation_construction.rs new file mode 100644 index 00000000..50473d33 --- /dev/null +++ b/src/geometry/rotation_construction.rs @@ -0,0 +1,31 @@ +use num::{Zero, One}; + +use alga::general::{ClosedAdd, ClosedMul}; + +use core::{SquareMatrix, Scalar}; +use core::dimension::DimName; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::RotationBase; + +impl RotationBase + where N: Scalar + Zero + One, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new square identity rotation of the given `dimension`. + #[inline] + pub fn identity() -> RotationBase { + Self::from_matrix_unchecked(SquareMatrix::::identity()) + } +} + +impl One for RotationBase + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn one() -> Self { + Self::identity() + } +} diff --git a/src/geometry/rotation_conversion.rs b/src/geometry/rotation_conversion.rs new file mode 100644 index 00000000..fc1c8be8 --- /dev/null +++ b/src/geometry/rotation_conversion.rs @@ -0,0 +1,195 @@ +use num::Zero; + +use alga::general::{Real, SubsetOf, SupersetOf}; +use alga::linear::Rotation as AlgaRotation; + +use core::{Matrix, SquareMatrix}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1, U3, U4}; +use core::storage::OwnedStorage; +use core::allocator::{OwnedAllocator, Allocator}; + +use geometry::{PointBase, TranslationBase, RotationBase, UnitQuaternionBase, OwnedUnitQuaternionBase, IsometryBase, + SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; + +/* + * This file provides the following conversions: + * ============================================= + * + * RotationBase -> RotationBase + * Rotation3 -> UnitQuaternion + * RotationBase -> IsometryBase + * RotationBase -> SimilarityBase + * RotationBase -> TransformBase + * RotationBase -> Matrix (homogeneous) + */ + + +impl SubsetOf> for RotationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> RotationBase { + RotationBase::from_matrix_unchecked(self.matrix().to_superset()) + } + + #[inline] + fn is_in_subset(rot: &RotationBase) -> bool { + ::is_convertible::<_, Matrix>(rot.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(rot: &RotationBase) -> Self { + RotationBase::from_matrix_unchecked(rot.matrix().to_subset_unchecked()) + } +} + + +impl SubsetOf> for RotationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator + + Allocator + + Allocator, + SB::Alloc: OwnedAllocator + + Allocator { + #[inline] + fn to_superset(&self) -> UnitQuaternionBase { + let q = OwnedUnitQuaternionBase::::from_rotation_matrix(self); + q.to_superset() + } + + #[inline] + fn is_in_subset(q: &UnitQuaternionBase) -> bool { + ::is_convertible::<_, OwnedUnitQuaternionBase>(q) + } + + #[inline] + unsafe fn from_superset_unchecked(q: &UnitQuaternionBase) -> Self { + let q: OwnedUnitQuaternionBase = ::convert_ref_unchecked(q); + q.to_rotation_matrix() + } +} + + +impl SubsetOf> for RotationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + R: AlgaRotation> + SupersetOf>, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> IsometryBase { + IsometryBase::from_parts(TranslationBase::identity(), ::convert_ref(self)) + } + + #[inline] + fn is_in_subset(iso: &IsometryBase) -> bool { + iso.translation.vector.is_zero() + } + + #[inline] + unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + ::convert_ref_unchecked(&iso.rotation) + } +} + + +impl SubsetOf> for RotationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + R: AlgaRotation> + SupersetOf>, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> SimilarityBase { + SimilarityBase::from_parts(TranslationBase::identity(), ::convert_ref(self), N2::one()) + } + + #[inline] + fn is_in_subset(sim: &SimilarityBase) -> bool { + sim.isometry.translation.vector.is_zero() && + sim.scaling() == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + ::convert_ref_unchecked(&sim.isometry.rotation) + } +} + + +impl SubsetOf> for RotationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + C: SuperTCategoryOf, + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator, DimNameSum>, + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf, SB>> for RotationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator, DimNameSum>, + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> SquareMatrix, SB> { + self.to_homogeneous().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + let rot = m.fixed_slice::(0, 0); + let bottom = m.fixed_slice::(D::dim(), 0); + + // Scalar types agree. + m.iter().all(|e| SupersetOf::::is_in_subset(e)) && + // The block part is a rotation. + rot.is_special_orthogonal(N2::default_epsilon() * ::convert(100.0)) && + // The bottom row is (0, 0, ..., 1) + bottom.iter().all(|e| e.is_zero()) && + m[(D::dim(), D::dim())] == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + let r = m.fixed_slice::(0, 0); + Self::from_matrix_unchecked(::convert_unchecked(r.into_owned())) + } +} diff --git a/src/geometry/rotation_ops.rs b/src/geometry/rotation_ops.rs new file mode 100644 index 00000000..c3643149 --- /dev/null +++ b/src/geometry/rotation_ops.rs @@ -0,0 +1,162 @@ +/* + * + * This provides the following operator overladings: + * + * Index<(usize, usize)> + * + * RotationBase × RotationBase + * RotationBase ÷ RotationBase + * RotationBase × Matrix + * Matrix × RotationBase + * Matrix ÷ RotationBase + * RotationBase × PointBase + * + * + * RotationBase ×= RotationBase + * Matrix ×= RotationBase + */ + + +use std::ops::{Mul, MulAssign, Div, DivAssign, Index}; +use num::Zero; + +use alga::general::{ClosedMul, ClosedAdd}; + +use core::{Scalar, Matrix, MatrixMul}; +use core::dimension::{Dim, DimName, U1}; +use core::constraint::{ShapeConstraint, AreMultipliable}; +use core::storage::{OwnedStorage, Storage}; +use core::allocator::{OwnedAllocator, Allocator}; + +use geometry::{PointBase, PointMul, RotationBase, OwnedRotation}; + +impl> Index<(usize, usize)> for RotationBase { + type Output = N; + + #[inline] + fn index(&self, row_col: (usize, usize)) -> &N { + self.matrix().index(row_col) + } +} + +// RotationBase × RotationBase +md_impl_all!( + Mul, mul; + (D, D), (D, D) for D: DimName; + self: RotationBase, right: RotationBase, Output = OwnedRotation; + [val val] => RotationBase::from_matrix_unchecked(self.unwrap() * right.unwrap()); + [ref val] => RotationBase::from_matrix_unchecked(self.matrix() * right.unwrap()); + [val ref] => RotationBase::from_matrix_unchecked(self.unwrap() * right.matrix()); + [ref ref] => RotationBase::from_matrix_unchecked(self.matrix() * right.matrix()); +); + +// RotationBase ÷ RotationBase +// FIXME: instead of calling inverse explicitely, could we just add a `mul_tr` or `mul_inv` method? +md_impl_all!( + Div, div; + (D, D), (D, D) for D: DimName; + self: RotationBase, right: RotationBase, Output = OwnedRotation; + [val val] => self * right.inverse(); + [ref val] => self * right.inverse(); + [val ref] => self * right.inverse(); + [ref ref] => self * right.inverse(); +); + +// RotationBase × Matrix +md_impl_all!( + Mul, mul; + (D1, D1), (R2, C2) for D1: DimName, R2: Dim, C2: Dim + where SA::Alloc: Allocator + where ShapeConstraint: AreMultipliable; + self: RotationBase, right: Matrix, Output = MatrixMul; + [val val] => self.unwrap() * right; + [ref val] => self.matrix() * right; + [val ref] => self.unwrap() * right; + [ref ref] => self.matrix() * right; +); + +// Matrix × RotationBase +md_impl_all!( + Mul, mul; + (R1, C1), (D2, D2) for R1: Dim, C1: Dim, D2: DimName + where SA::Alloc: Allocator + where ShapeConstraint: AreMultipliable; + self: Matrix, right: RotationBase, Output = MatrixMul; + [val val] => self * right.unwrap(); + [ref val] => self * right.unwrap(); + [val ref] => self * right.matrix(); + [ref ref] => self * right.matrix(); +); + +// Matrix ÷ RotationBase +md_impl_all!( + Div, div; + (R1, C1), (D2, D2) for R1: Dim, C1: Dim, D2: DimName + where SA::Alloc: Allocator + where ShapeConstraint: AreMultipliable; + self: Matrix, right: RotationBase, Output = MatrixMul; + [val val] => self * right.inverse(); + [ref val] => self * right.inverse(); + [val ref] => self * right.inverse(); + [ref ref] => self * right.inverse(); +); + + +// RotationBase × PointBase +// FIXME: we don't handle properly non-zero origins here. Do we want this to be the intended +// behavior? +md_impl_all!( + Mul, mul; + (D, D), (D, U1) for D: DimName + where SA::Alloc: Allocator + where ShapeConstraint: AreMultipliable; + self: RotationBase, right: PointBase, Output = PointMul; + [val val] => self.unwrap() * right; + [ref val] => self.matrix() * right; + [val ref] => self.unwrap() * right; + [ref ref] => self.matrix() * right; +); + + +// RotationBase *= RotationBase +// FIXME: try not to call `inverse()` explicitly. + +md_assign_impl_all!( + MulAssign, mul_assign; + (D, D), (D, D) for D: DimName; + self: RotationBase, right: RotationBase; + [val] => unsafe { self.matrix_mut().mul_assign(right.unwrap()) }; + [ref] => unsafe { self.matrix_mut().mul_assign(right.matrix()) }; +); + + +md_assign_impl_all!( + DivAssign, div_assign; + (D, D), (D, D) for D: DimName; + self: RotationBase, right: RotationBase; + [val] => unsafe { self.matrix_mut().mul_assign(right.inverse().unwrap()) }; + [ref] => unsafe { self.matrix_mut().mul_assign(right.inverse().matrix()) }; +); + +// Matrix *= RotationBase +// FIXME: try not to call `inverse()` explicitly. +// FIXME: this shares the same limitations as for the current impl. of MulAssign for matrices. +// (In particular the number of matrix column must be equal to the number of rotation columns, +// i.e., equal to the rotation dimension. + +md_assign_impl_all!( + MulAssign, mul_assign; + (R1, C1), (C1, C1) for R1: DimName, C1: DimName; + self: Matrix, right: RotationBase; + [val] => self.mul_assign(right.unwrap()); + [ref] => self.mul_assign(right.matrix()); +); + + +md_assign_impl_all!( + DivAssign, div_assign; + (R1, C1), (C1, C1) for R1: DimName, C1: DimName; + self: Matrix, right: RotationBase; + [val] => self.mul_assign(right.inverse().unwrap()); + [ref] => self.mul_assign(right.inverse().matrix()); +); diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs new file mode 100644 index 00000000..1a1190c4 --- /dev/null +++ b/src/geometry/rotation_specialization.rs @@ -0,0 +1,377 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use std::ops::Neg; +use num::Zero; +use rand::{Rand, Rng}; +use alga::general::Real; + +use core::{Unit, ColumnVector, SquareMatrix, OwnedSquareMatrix, OwnedColumnVector, Vector3}; +use core::dimension::{U1, U2, U3}; +use core::storage::{Storage, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; + +use geometry::{RotationBase, OwnedRotation, UnitComplex}; + + +/* + * + * 2D RotationBase matrix. + * + */ +impl RotationBase +where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Builds a 2 dimensional rotation matrix from an angle in radian. + pub fn new(angle: N) -> Self { + let (sia, coa) = angle.sin_cos(); + Self::from_matrix_unchecked(SquareMatrix::::new(coa, -sia, sia, coa)) + } + + /// Builds a 2 dimensional rotation matrix from an angle in radian wrapped in a 1-dimensional vector. + /// + /// Equivalent to `Self::new(axisangle[0])`. + #[inline] + pub fn from_scaled_axis>(axisangle: ColumnVector) -> Self { + Self::new(axisangle[0]) + } + + /// The rotation matrix required to align `a` and `b` but with its angl. + /// + /// This is the rotation `R` such that `(R * a).angle(b) == 0 && (R * a).dot(b).is_positive()`. + #[inline] + pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Self + where SB: Storage, + SC: Storage { + UnitComplex::rotation_between(a, b).to_rotation_matrix() + } + + /// The smallest rotation needed to make `a` and `b` collinear and point toward the same + /// direction, raised to the power `s`. + #[inline] + pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Self + where SB: Storage, + SC: Storage { + UnitComplex::scaled_rotation_between(a, b, s).to_rotation_matrix() + } +} + +impl RotationBase +where N: Real, + S: Storage { + /// The rotation angle. + #[inline] + pub fn angle(&self) -> N { + self.matrix()[(1, 0)].atan2(self.matrix()[(0, 0)]) + } + + /// The rotation angle needed to make `self` and `other` coincide. + #[inline] + pub fn angle_to>(&self, other: &RotationBase) -> N { + self.rotation_to(other).angle() + } + + /// The rotation matrix needed to make `self` and `other` coincide. + /// + /// The result is such that: `self.rotation_to(other) * self == other`. + #[inline] + pub fn rotation_to(&self, other: &RotationBase) -> OwnedRotation + where SB: Storage { + other * self.inverse() + } + + #[inline] + pub fn powf(&self, n: N) -> OwnedRotation { + OwnedRotation::<_, _, S::Alloc>::new(self.angle() * n) + } + + /// The rotation angle returned as a 1-dimensional vector. + #[inline] + pub fn scaled_axis(&self) -> OwnedColumnVector + where S::Alloc: Allocator { + ColumnVector::<_, U1, _>::new(self.angle()) + } +} + +impl Rand for RotationBase +where N: Real + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut R) -> Self { + Self::new(rng.gen()) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for RotationBase +where N: Real + Arbitrary, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(g: &mut G) -> Self { + Self::new(N::arbitrary(g)) + } +} + + +/* + * + * 3D RotationBase matrix. + * + */ +impl RotationBase +where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + + /// Builds a 3 dimensional rotation matrix from an axis and an angle. + /// + /// # Arguments + /// * `axisangle` - A vector representing the rotation. Its magnitude is the amount of rotation + /// in radian. Its direction is the axis of rotation. + pub fn new>(axisangle: ColumnVector) -> Self { + let (axis, angle) = Unit::new_and_get(axisangle.into_owned()); + Self::from_axis_angle(&axis, angle) + } + + /// Builds a 3D rotation matrix from an axis scaled by the rotation angle. + pub fn from_scaled_axis>(axisangle: ColumnVector) -> Self { + Self::new(axisangle) + } + + /// Builds a 3D rotation matrix from an axis and a rotation angle. + pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self + where SB: Storage { + if angle.is_zero() { + Self::identity() + } + else { + let ux = axis.as_ref()[0]; + let uy = axis.as_ref()[1]; + let uz = axis.as_ref()[2]; + let sqx = ux * ux; + let sqy = uy * uy; + let sqz = uz * uz; + let (sin, cos) = angle.sin_cos(); + let one_m_cos = N::one() - cos; + + Self::from_matrix_unchecked( + SquareMatrix::::new( + (sqx + (N::one() - sqx) * cos), + (ux * uy * one_m_cos - uz * sin), + (ux * uz * one_m_cos + uy * sin), + + (ux * uy * one_m_cos + uz * sin), + (sqy + (N::one() - sqy) * cos), + (uy * uz * one_m_cos - ux * sin), + + (ux * uz * one_m_cos - uy * sin), + (uy * uz * one_m_cos + ux * sin), + (sqz + (N::one() - sqz) * cos))) + } + } + + /// Creates a new rotation from Euler angles. + /// + /// The primitive rotations are applied in order: 1 roll − 2 pitch − 3 yaw. + pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> Self { + let (sr, cr) = roll.sin_cos(); + let (sp, cp) = pitch.sin_cos(); + let (sy, cy) = yaw.sin_cos(); + + Self::from_matrix_unchecked( + SquareMatrix::::new( + cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr, + sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr, + -sp, cp * sr, cp * cr) + ) + } + + /// Creates a rotation that corresponds to the local frame of an observer standing at the + /// origin and looking toward `dir`. + /// + /// It maps the view direction `dir` to the positive `z` axis. + /// + /// # Arguments + /// * dir - The look direction, that is, direction the matrix `z` axis will be aligned with. + /// * up - The vertical direction. The only requirement of this parameter is to not be + /// collinear + /// to `dir`. Non-collinearity is not checked. + #[inline] + pub fn new_observer_frame(dir: &ColumnVector, up: &ColumnVector) -> Self + where SB: Storage, + SC: Storage { + let zaxis = dir.normalize(); + let xaxis = up.cross(&zaxis).normalize(); + let yaxis = zaxis.cross(&xaxis).normalize(); + + Self::from_matrix_unchecked(SquareMatrix::::new( + xaxis.x, yaxis.x, zaxis.x, + xaxis.y, yaxis.y, zaxis.y, + xaxis.z, yaxis.z, zaxis.z)) + } + + + /// Builds a right-handed look-at view matrix without translation. + /// + /// This conforms to the common notion of right handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_rh(dir: &ColumnVector, up: &ColumnVector) -> Self + where SB: Storage, + SC: Storage { + Self::new_observer_frame(&dir.neg(), up).inverse() + } + + /// Builds a left-handed look-at view matrix without translation. + /// + /// This conforms to the common notion of left handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_lh(dir: &ColumnVector, up: &ColumnVector) -> Self + where SB: Storage, + SC: Storage { + Self::new_observer_frame(dir, up).inverse() + } + + /// The rotation matrix required to align `a` and `b` but with its angl. + /// + /// This is the rotation `R` such that `(R * a).angle(b) == 0 && (R * a).dot(b).is_positive()`. + #[inline] + pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option + where SB: Storage, + SC: Storage { + Self::scaled_rotation_between(a, b, N::one()) + } + + /// The smallest rotation needed to make `a` and `b` collinear and point toward the same + /// direction, raised to the power `s`. + #[inline] + pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, n: N) + -> Option + where SB: Storage, + SC: Storage { + // FIXME: code duplication with RotationBase. + if let (Some(na), Some(nb)) = (a.try_normalize(N::zero()), b.try_normalize(N::zero())) { + let c = na.cross(&nb); + + if let Some(axis) = Unit::try_new(c, N::default_epsilon()) { + return Some(Self::from_axis_angle(&axis, na.dot(&nb).acos() * n)) + } + + // Zero or PI. + 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; + } + } + + Some(Self::identity()) + } +} + +impl RotationBase +where N: Real, + S: Storage { + /// The rotation angle. + #[inline] + pub fn angle(&self) -> N { + ((self.matrix()[(0, 0)] + self.matrix()[(1, 1)] + self.matrix()[(2, 2)] - N::one()) / ::convert(2.0)).acos() + } +} + +impl RotationBase +where N: Real, + S: Storage, + S::Alloc: Allocator { + /// The rotation axis. Returns `None` if the rotation angle is zero or PI. + #[inline] + pub fn axis(&self) -> Option>> { + let axis = OwnedColumnVector::::new( + self.matrix()[(2, 1)] - self.matrix()[(1, 2)], + self.matrix()[(0, 2)] - self.matrix()[(2, 0)], + self.matrix()[(1, 0)] - self.matrix()[(0, 1)]); + + Unit::try_new(axis, N::default_epsilon()) + } + + /// The rotation axis multiplied by the rotation angle. + #[inline] + pub fn scaled_axis(&self) -> OwnedColumnVector { + if let Some(axis) = self.axis() { + axis.unwrap() * self.angle() + } + else { + ColumnVector::zero() + } + } + + /// The rotation angle needed to make `self` and `other` coincide. + #[inline] + pub fn angle_to>(&self, other: &RotationBase) -> N { + self.rotation_to(other).angle() + } + + /// The rotation matrix needed to make `self` and `other` coincide. + /// + /// The result is such that: `self.rotation_to(other) * self == other`. + #[inline] + pub fn rotation_to(&self, other: &RotationBase) -> OwnedRotation + where SB: Storage { + other * self.inverse() + } + + #[inline] + pub fn powf(&self, n: N) -> OwnedRotation { + if let Some(axis) = self.axis() { + OwnedRotation::<_, _, S::Alloc>::from_axis_angle(&axis, self.angle() * n) + } + else if self.matrix()[(0, 0)] < N::zero() { + let minus_id = OwnedSquareMatrix::::from_diagonal_element(-N::one()); + OwnedRotation::<_, _, S::Alloc>::from_matrix_unchecked(minus_id) + } + else { + OwnedRotation::<_, _, S::Alloc>::identity() + } + } +} + +impl Rand for RotationBase +where N: Real + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator + + Allocator { + #[inline] + fn rand(rng: &mut R) -> Self { + Self::new(Vector3::rand(rng)) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for RotationBase +where N: Real + Arbitrary, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator + + Allocator { + #[inline] + fn arbitrary(g: &mut G) -> Self { + Self::new(Vector3::arbitrary(g)) + } +} diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs new file mode 100644 index 00000000..7b4d76f8 --- /dev/null +++ b/src/geometry/similarity.rs @@ -0,0 +1,269 @@ +use std::fmt; +use approx::ApproxEq; + +use alga::general::{ClosedMul, Real, SubsetOf}; +use alga::linear::Rotation; + +use core::{Scalar, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::{Storage, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; +use geometry::{PointBase, TranslationBase, IsometryBase}; + +/// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation. +#[repr(C)] +#[derive(Hash, Debug, Clone, Copy)] +pub struct SimilarityBase { + pub isometry: IsometryBase, + scaling: N +} + +impl SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + /// Creates a new similarity from its rotational and translational parts. + #[inline] + pub fn from_parts(translation: TranslationBase, rotation: R, scaling: N) -> SimilarityBase { + SimilarityBase::from_isometry(IsometryBase::from_parts(translation, rotation), scaling) + } + + /// Creates a new similarity from its rotational and translational parts. + #[inline] + pub fn from_isometry(isometry: IsometryBase, scaling: N) -> SimilarityBase { + assert!(!relative_eq!(scaling, N::zero()), "The scaling factor must not be zero."); + + SimilarityBase { + isometry: isometry, + scaling: scaling + } + } + + /// Creates a new similarity that applies only a scaling factor. + #[inline] + pub fn from_scaling(scaling: N) -> SimilarityBase { + Self::from_isometry(IsometryBase::identity(), scaling) + } + + /// Inverts `self`. + #[inline] + pub fn inverse(&self) -> SimilarityBase { + let mut res = self.clone(); + res.inverse_mut(); + res + } + + /// Inverts `self` in-place. + #[inline] + pub fn inverse_mut(&mut self) { + self.scaling = N::one() / self.scaling; + self.isometry.inverse_mut(); + self.isometry.translation.vector *= self.scaling; + } + + /// The scaling factor of this similarity transformation. + #[inline] + pub fn scaling(&self) -> N { + self.scaling + } + + /// The scaling factor of this similarity transformation. + #[inline] + pub fn set_scaling(&mut self, scaling: N) { + assert!(!relative_eq!(scaling, N::zero()), "The similarity scaling factor must not be zero."); + + self.scaling = scaling; + } + + /// The similarity transformation that applies a scaling factor `scaling` before `self`. + #[inline] + pub fn prepend_scaling(&self, scaling: N) -> Self { + assert!(!relative_eq!(scaling, N::zero()), "The similarity scaling factor must not be zero."); + + Self::from_isometry(self.isometry.clone(), self.scaling * scaling) + } + + /// The similarity transformation that applies a scaling factor `scaling` after `self`. + #[inline] + pub fn append_scaling(&self, scaling: N) -> Self { + assert!(!relative_eq!(scaling, N::zero()), "The similarity scaling factor must not be zero."); + + Self::from_parts( + TranslationBase::from_vector(&self.isometry.translation.vector * scaling), + self.isometry.rotation.clone(), + self.scaling * scaling) + } + + /// Sets `self` to the similarity transformation that applies a scaling factor `scaling` before `self`. + #[inline] + pub fn prepend_scaling_mut(&mut self, scaling: N) { + assert!(!relative_eq!(scaling, N::zero()), "The similarity scaling factor must not be zero."); + + self.scaling *= scaling + } + + /// Sets `self` to the similarity transformation that applies a scaling factor `scaling` after `self`. + #[inline] + pub fn append_scaling_mut(&mut self, scaling: N) { + assert!(!relative_eq!(scaling, N::zero()), "The similarity scaling factor must not be zero."); + + self.isometry.translation.vector *= scaling; + self.scaling *= scaling; + } + + /// Appends to `self` the given translation in-place. + #[inline] + pub fn append_translation_mut(&mut self, t: &TranslationBase) { + self.isometry.append_translation_mut(t) + } + + /// Appends to `self` the given rotation in-place. + #[inline] + pub fn append_rotation_mut(&mut self, r: &R) { + self.isometry.append_rotation_mut(r) + } + + /// Appends in-place to `self` a rotation centered at the point `p`, i.e., the rotation that + /// lets `p` invariant. + #[inline] + pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &PointBase) { + self.isometry.append_rotation_wrt_point_mut(r, p) + } + + /// Appends in-place to `self` a rotation centered at the point with coordinates + /// `self.translation`. + #[inline] + pub fn append_rotation_wrt_center_mut(&mut self, r: &R) { + self.isometry.append_rotation_wrt_center_mut(r) + } +} + + +// NOTE: we don't require `R: Rotation<...>` here becaus this is not useful for the implementation +// and makes it harde to use it, e.g., for Transform × Isometry implementation. +// This is OK since all constructors of the isometry enforce the Rotation bound already (and +// explicit struct construction is prevented by the private scaling factor). +impl SimilarityBase + where N: Scalar + ClosedMul, + S: Storage { + /// Converts this similarity into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + where D: DimNameAdd, + R: SubsetOf, S::Alloc>>, + S::Alloc: Allocator + + Allocator, DimNameSum> { + let mut res = self.isometry.to_homogeneous(); + + for e in res.fixed_slice_mut::(0, 0).iter_mut() { + *e *= self.scaling + } + + res + } +} + + +impl Eq for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation> + Eq, + S::Alloc: OwnedAllocator { +} + +impl PartialEq for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation> + PartialEq, + S::Alloc: OwnedAllocator { + #[inline] + fn eq(&self, right: &SimilarityBase) -> bool { + self.isometry == right.isometry && self.scaling == right.scaling + } +} + +impl ApproxEq for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation> + ApproxEq, + S::Alloc: OwnedAllocator, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.isometry.relative_eq(&other.isometry, epsilon, max_relative) && + self.scaling.relative_eq(&other.scaling, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.isometry.ulps_eq(&other.isometry, epsilon, max_ulps) && + self.scaling.ulps_eq(&other.scaling, epsilon, max_ulps) + } +} + +/* + * + * Display + * + */ +impl fmt::Display for SimilarityBase + where N: Real + fmt::Display, + S: OwnedStorage, + R: Rotation> + fmt::Display, + S::Alloc: OwnedAllocator + Allocator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let precision = f.precision().unwrap_or(3); + + try!(writeln!(f, "SimilarityBase {{")); + try!(write!(f, "{:.*}", precision, self.isometry)); + try!(write!(f, "Scaling: {:.*}", precision, self.scaling)); + writeln!(f, "}}") + } +} + +/* +// /* +// * +// * ToHomogeneous +// * +// */ +// impl ToHomogeneous<$homogeneous> for $t { +// #[inline] +// fn to_homogeneous(&self) -> $homogeneous { +// self.vector.to_homogeneous() +// } +// } + + +// /* +// * +// * Absolute +// * +// */ +// impl Absolute for $t { +// type AbsoluteValue = $submatrix; +// +// #[inline] +// fn abs(m: &$t) -> $submatrix { +// Absolute::abs(&m.submatrix) +// } +// } +*/ diff --git a/src/geometry/similarity_alga.rs b/src/geometry/similarity_alga.rs new file mode 100644 index 00000000..5959d6f1 --- /dev/null +++ b/src/geometry/similarity_alga.rs @@ -0,0 +1,184 @@ +use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, + AbstractSemigroup, Real, Inverse, Multiplicative, Identity}; +use alga::linear::{Transformation, AffineTransformation, Rotation, Similarity, ProjectiveTransformation}; + +use core::ColumnVector; +use core::dimension::{DimName, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{SimilarityBase, TranslationBase, PointBase}; + + +/* + * + * Algebraic structures. + * + */ +impl Identity for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl Inverse for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Self { + self.inverse() + } + + #[inline] + fn inverse_mut(&mut self) { + self.inverse_mut() + } +} + +impl AbstractMagma for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +macro_rules! impl_multiplicative_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl $marker<$operator> for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { } + )*} +); + +impl_multiplicative_structures!( + AbstractSemigroup, + AbstractMonoid, + AbstractQuasigroup, + AbstractLoop, + AbstractGroup +); + +/* + * + * Transformation groups. + * + */ +impl Transformation> for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + self * pt + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self * v + } +} + +impl ProjectiveTransformation> for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + self.isometry.inverse_transform_point(pt) / self.scaling() + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self.isometry.inverse_transform_vector(v) / self.scaling() + } +} + +impl AffineTransformation> for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + type NonUniformScaling = N; + type Rotation = R; + type Translation = TranslationBase; + + #[inline] + fn decompose(&self) -> (TranslationBase, R, N, R) { + (self.isometry.translation.clone(), self.isometry.rotation.clone(), self.scaling(), R::identity()) + } + + #[inline] + fn append_translation(&self, t: &Self::Translation) -> Self { + t * self + } + + #[inline] + fn prepend_translation(&self, t: &Self::Translation) -> Self { + self * t + } + + #[inline] + fn append_rotation(&self, r: &Self::Rotation) -> Self { + SimilarityBase::from_isometry(self.isometry.append_rotation(r), self.scaling()) + } + + #[inline] + fn prepend_rotation(&self, r: &Self::Rotation) -> Self { + self * r + } + + #[inline] + fn append_scaling(&self, s: &Self::NonUniformScaling) -> Self { + self.append_scaling(*s) + } + + #[inline] + fn prepend_scaling(&self, s: &Self::NonUniformScaling) -> Self { + self.prepend_scaling(*s) + } + + #[inline] + fn append_rotation_wrt_point(&self, r: &Self::Rotation, p: &PointBase) -> Option { + let mut res = self.clone(); + res.append_rotation_wrt_point_mut(r, p); + Some(res) + } +} + +impl Similarity> for SimilarityBase + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + type Scaling = N; + + #[inline] + fn translation(&self) -> TranslationBase { + self.isometry.translation() + } + + #[inline] + fn rotation(&self) -> R { + self.isometry.rotation() + } + + #[inline] + fn scaling(&self) -> N { + self.scaling() + } +} diff --git a/src/geometry/similarity_alias.rs b/src/geometry/similarity_alias.rs new file mode 100644 index 00000000..870d3a88 --- /dev/null +++ b/src/geometry/similarity_alias.rs @@ -0,0 +1,19 @@ +use core::MatrixArray; +use core::dimension::{U1, U2, U3}; + +use geometry::{Rotation, SimilarityBase, UnitQuaternion, UnitComplex}; + +/// A D-dimensional similarity. +pub type Similarity = SimilarityBase, Rotation>; + +/// A 2-dimensional similarity. +pub type Similarity2 = SimilarityBase, UnitComplex>; + +/// A 3-dimensional similarity. +pub type Similarity3 = SimilarityBase, UnitQuaternion>; + +/// A 3-dimensional similarity using a rotation matrix for its rotation part. +pub type SimilarityMatrix2 = Similarity; + +/// A 3-dimensional similarity using a rotation matrix for its rotation part. +pub type SimilarityMatrix3 = Similarity; diff --git a/src/geometry/similarity_construction.rs b/src/geometry/similarity_construction.rs new file mode 100644 index 00000000..443de37a --- /dev/null +++ b/src/geometry/similarity_construction.rs @@ -0,0 +1,188 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use num::One; +use rand::{Rng, Rand}; + +use alga::general::Real; +use alga::linear::Rotation as AlgaRotation; + +use core::ColumnVector; +use core::dimension::{DimName, U1, U2, U3, U4}; +use core::allocator::{OwnedAllocator, Allocator}; +use core::storage::OwnedStorage; + +use geometry::{PointBase, TranslationBase, RotationBase, SimilarityBase, UnitQuaternionBase, IsometryBase}; + + +impl SimilarityBase + where N: Real, + S: OwnedStorage, + R: AlgaRotation>, + S::Alloc: OwnedAllocator { + /// Creates a new identity similarity. + #[inline] + pub fn identity() -> Self { + Self::from_isometry(IsometryBase::identity(), N::one()) + } +} + +impl One for SimilarityBase + where N: Real, + S: OwnedStorage, + R: AlgaRotation>, + S::Alloc: OwnedAllocator { + /// Creates a new identity similarity. + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Rand for SimilarityBase + where N: Real + Rand, + S: OwnedStorage, + R: AlgaRotation> + Rand, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut G) -> Self { + let mut s = rng.gen(); + while relative_eq!(s, N::zero()) { + s = rng.gen() + } + + Self::from_isometry(rng.gen(), s) + } +} + +impl SimilarityBase + where N: Real, + S: OwnedStorage, + R: AlgaRotation>, + S::Alloc: OwnedAllocator { + /// The similarity that applies tha scaling factor `scaling`, followed by the rotation `r` with + /// its axis passing through the point `p`. + #[inline] + pub fn rotation_wrt_point(r: R, p: PointBase, scaling: N) -> Self { + let shift = r.transform_vector(&-&p.coords); + Self::from_parts(TranslationBase::from_vector(shift + p.coords), r, scaling) + } +} + +#[cfg(feature = "arbitrary")] +impl Arbitrary for SimilarityBase + where N: Real + Arbitrary + Send, + S: OwnedStorage + Send, + R: AlgaRotation> + Arbitrary + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(rng: &mut G) -> Self { + let mut s = Arbitrary::arbitrary(rng); + while relative_eq!(s, N::zero()) { + s = Arbitrary::arbitrary(rng) + } + + Self::from_isometry(Arbitrary::arbitrary(rng), s) + } +} + +/* + * + * Constructors for various static dimensions. + * + */ + +// 2D rotation. +impl SimilarityBase> + where N: Real, + S: OwnedStorage, + SR: OwnedStorage, + S::Alloc: OwnedAllocator, + SR::Alloc: OwnedAllocator { + /// Creates a new similarity from a translation and a rotation angle. + #[inline] + pub fn new(translation: ColumnVector, angle: N, scaling: N) -> Self { + Self::from_parts(TranslationBase::from_vector(translation), RotationBase::::new(angle), scaling) + } +} + +// 3D rotation. +macro_rules! similarity_construction_impl( + ($Rot: ty, $RotId: ident, $RRDim: ty, $RCDim: ty) => { + impl SimilarityBase + where N: Real, + S: OwnedStorage, + SR: OwnedStorage, + S::Alloc: OwnedAllocator, + SR::Alloc: OwnedAllocator + + Allocator { + /// Creates a new similarity from a translation, rotation axis-angle, and scaling + /// factor. + #[inline] + pub fn new(translation: ColumnVector, axisangle: ColumnVector, scaling: N) -> Self { + Self::from_isometry(IsometryBase::<_, _, _, $Rot>::new(translation, axisangle), scaling) + } + + /// Creates an similarity that corresponds to the a scaling factor and a local frame of + /// an observer standing at the point `eye` and looking toward `target`. + /// + /// It maps the view direction `target - eye` to the positive `z` axis and the origin to the + /// `eye`. + /// + /// # Arguments + /// * eye - The observer position. + /// * target - The target position. + /// * up - Vertical direction. The only requirement of this parameter is to not be collinear + /// to `eye - at`. Non-collinearity is not checked. + #[inline] + pub fn new_observer_frame(eye: &PointBase, + target: &PointBase, + up: &ColumnVector, + scaling: N) + -> Self { + Self::from_isometry(IsometryBase::<_, _, _, $Rot>::new_observer_frame(eye, target, up), scaling) + } + + /// Builds a right-handed look-at view matrix including scaling factor. + /// + /// This conforms to the common notion of right handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_rh(eye: &PointBase, + target: &PointBase, + up: &ColumnVector, + scaling: N) + -> Self { + Self::from_isometry(IsometryBase::<_, _, _, $Rot>::look_at_rh(eye, target, up), scaling) + } + + /// Builds a left-handed look-at view matrix including a scaling factor. + /// + /// This conforms to the common notion of left handed look-at matrix from the computer + /// graphics community. + /// + /// # Arguments + /// * eye - The eye position. + /// * target - The target position. + /// * up - A vector approximately aligned with required the vertical axis. The only + /// requirement of this parameter is to not be collinear to `target - eye`. + #[inline] + pub fn look_at_lh(eye: &PointBase, + target: &PointBase, + up: &ColumnVector, + scaling: N) + -> Self { + Self::from_isometry(IsometryBase::<_, _, _, $Rot>::look_at_lh(eye, target, up), scaling) + } + } + } +); + +similarity_construction_impl!(RotationBase, RotationBase, U3, U3); +similarity_construction_impl!(UnitQuaternionBase, UnitQuaternionBase, U4, U1); diff --git a/src/geometry/similarity_conversion.rs b/src/geometry/similarity_conversion.rs new file mode 100644 index 00000000..e629db77 --- /dev/null +++ b/src/geometry/similarity_conversion.rs @@ -0,0 +1,163 @@ +use alga::general::{Real, SubsetOf, SupersetOf}; +use alga::linear::Rotation; + +use core::{SquareMatrix, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; + +use geometry::{PointBase, TranslationBase, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; + +/* + * This file provides the following conversions: + * ============================================= + * + * SimilarityBase -> SimilarityBase + * SimilarityBase -> TransformBase + * SimilarityBase -> Matrix (homogeneous) + */ + + +impl SubsetOf> for SimilarityBase + where N1: Real + SubsetOf, + N2: Real + SupersetOf, + R1: Rotation> + SubsetOf, + R2: Rotation>, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> SimilarityBase { + SimilarityBase::from_isometry( + self.isometry.to_superset(), + self.scaling().to_superset() + ) + } + + #[inline] + fn is_in_subset(sim: &SimilarityBase) -> bool { + ::is_convertible::<_, IsometryBase>(&sim.isometry) && + ::is_convertible::<_, N1>(&sim.scaling()) + } + + #[inline] + unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + SimilarityBase::from_isometry( + sim.isometry.to_subset_unchecked(), + sim.scaling().to_subset_unchecked() + ) + } +} + + +impl SubsetOf> for SimilarityBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + C: SuperTCategoryOf, + R: Rotation> + + SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() + SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum>, // needed by R + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + // needed by: mm.fixed_slice_mut + Allocator + // needed by: m.fixed_slice + Allocator { // needed by: m.fixed_slice + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf, SB>> for SimilarityBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + R: Rotation> + + SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() + SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum>, // needed by R + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + // needed by: mm.fixed_slice_mut + Allocator + // needed by: m.fixed_slice + Allocator { // needed by: m.fixed_slice + #[inline] + fn to_superset(&self) -> SquareMatrix, SB> { + self.to_homogeneous().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + let mut rot = m.fixed_slice::(0, 0).clone_owned(); + if rot.fixed_columns_mut::(0).try_normalize_mut(N2::zero()).is_some() && + rot.fixed_columns_mut::(1).try_normalize_mut(N2::zero()).is_some() && + rot.fixed_columns_mut::(2).try_normalize_mut(N2::zero()).is_some() { + + // FIXME: could we avoid explicit the computation of the determinant? + // (its sign is needed to see if the scaling factor is negative). + if rot.determinant() < N2::zero() { + rot.fixed_columns_mut::(0).neg_mut(); + rot.fixed_columns_mut::(1).neg_mut(); + rot.fixed_columns_mut::(2).neg_mut(); + } + + let bottom = m.fixed_slice::(D::dim(), 0); + // Scalar types agree. + m.iter().all(|e| SupersetOf::::is_in_subset(e)) && + // The normalized block part is a rotation. + // rot.is_special_orthogonal(N2::default_epsilon().sqrt()) && + // The bottom row is (0, 0, ..., 1) + bottom.iter().all(|e| e.is_zero()) && + m[(D::dim(), D::dim())] == N2::one() + } + else { + false + } + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + let mut mm = m.clone_owned(); + let na = mm.fixed_slice_mut::(0, 0).normalize_mut(); + let nb = mm.fixed_slice_mut::(0, 1).normalize_mut(); + let nc = mm.fixed_slice_mut::(0, 2).normalize_mut(); + + let mut scale = (na + nb + nc) / ::convert(3.0); // We take the mean, for robustness. + + // FIXME: could we avoid the explicit computation of the determinant? + // (its sign is needed to see if the scaling factor is negative). + if mm.fixed_slice::(0, 0).determinant() < N2::zero() { + mm.fixed_slice_mut::(0, 0).neg_mut(); + mm.fixed_slice_mut::(0, 1).neg_mut(); + mm.fixed_slice_mut::(0, 2).neg_mut(); + scale = -scale; + } + + let t = m.fixed_slice::(0, D::dim()).into_owned(); + let t = TranslationBase::from_vector(::convert_unchecked(t)); + + Self::from_parts(t, ::convert_unchecked(mm), ::convert_unchecked(scale)) + } +} diff --git a/src/geometry/similarity_ops.rs b/src/geometry/similarity_ops.rs new file mode 100644 index 00000000..59274db1 --- /dev/null +++ b/src/geometry/similarity_ops.rs @@ -0,0 +1,512 @@ +use std::ops::{Mul, MulAssign, Div, DivAssign}; + +use alga::general::Real; +use alga::linear::Rotation; + +use core::ColumnVector; +use core::dimension::{DimName, U1, U3, U4}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{PointBase, RotationBase, SimilarityBase, TranslationBase, UnitQuaternionBase, IsometryBase}; + +// FIXME: there are several cloning of rotations that we could probably get rid of (but we didn't +// yet because that would require to add a bound like `where for<'a, 'b> &'a R: Mul<&'b R, Output = R>` +// which is quite ugly. + +/* + * + * In this file, we provide: + * ========================= + * + * + * (Operators) + * + * SimilarityBase × SimilarityBase + * SimilarityBase × R + * SimilarityBase × IsometryBase + * + * IsometryBase × SimilarityBase + * IsometryBase ÷ SimilarityBase + * + * + * SimilarityBase ÷ SimilarityBase + * SimilarityBase ÷ R + * SimilarityBase ÷ IsometryBase + * + * SimilarityBase × PointBase + * SimilarityBase × ColumnVector + * + * + * SimilarityBase × TranslationBase + * TranslationBase × SimilarityBase + * + * NOTE: The following are provided explicitly because we can't have R × SimilarityBase. + * RotationBase × SimilarityBase + * UnitQuaternion × SimilarityBase + * + * RotationBase ÷ SimilarityBase + * UnitQuaternion ÷ SimilarityBase + * + * (Assignment Operators) + * + * SimilarityBase ×= TranslationBase + * + * SimilarityBase ×= SimilarityBase + * SimilarityBase ×= IsometryBase + * SimilarityBase ×= R + * + * SimilarityBase ÷= SimilarityBase + * SimilarityBase ÷= IsometryBase + * SimilarityBase ÷= R + * + */ + + +// XXX: code duplication: those macros are the same as for the isometry. +macro_rules! similarity_binop_impl( + ($Op: ident, $op: ident; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N, D: DimName, S, R> $Op<$Rhs> for $Lhs + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + type Output = $Output; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + +macro_rules! similarity_binop_impl_all( + ($Op: ident, $op: ident; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + [val val] => $action_val_val: expr; + [ref val] => $action_ref_val: expr; + [val ref] => $action_val_ref: expr; + [ref ref] => $action_ref_ref: expr;) => { + similarity_binop_impl!( + $Op, $op; + $lhs: $Lhs, $rhs: $Rhs, Output = $Output; + $action_val_val; ); + + similarity_binop_impl!( + $Op, $op; + $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Output; + $action_ref_val; 'a); + + similarity_binop_impl!( + $Op, $op; + $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_val_ref; 'b); + + similarity_binop_impl!( + $Op, $op; + $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_ref_ref; 'a, 'b); + } +); + +macro_rules! similarity_binop_assign_impl_all( + ($OpAssign: ident, $op_assign: ident; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; + [val] => $action_val: expr; + [ref] => $action_ref: expr;) => { + impl $OpAssign<$Rhs> for $Lhs + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn $op_assign(&mut $lhs, $rhs: $Rhs) { + $action_val + } + } + + impl<'b, N, D: DimName, S, R> $OpAssign<&'b $Rhs> for $Lhs + where N: Real, + S: OwnedStorage, + R: Rotation>, + S::Alloc: OwnedAllocator { + #[inline] + fn $op_assign(&mut $lhs, $rhs: &'b $Rhs) { + $action_ref + } + } + } +); + +// SimilarityBase × SimilarityBase +// SimilarityBase ÷ SimilarityBase +similarity_binop_impl_all!( + Mul, mul; + self: SimilarityBase, rhs: SimilarityBase, Output = SimilarityBase; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let mut res = self * &rhs.isometry; + res.prepend_scaling_mut(rhs.scaling()); + res + }; +); + + +similarity_binop_impl_all!( + Div, div; + self: SimilarityBase, rhs: SimilarityBase, Output = SimilarityBase; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + + +// SimilarityBase ×= TranslationBase +similarity_binop_assign_impl_all!( + MulAssign, mul_assign; + self: SimilarityBase, rhs: TranslationBase; + [val] => *self *= &rhs; + [ref] => { + let shift = self.isometry.rotation.transform_vector(&rhs.vector) * self.scaling(); + self.isometry.translation.vector += shift; + }; +); + + +// SimilarityBase ×= SimilarityBase +// SimilarityBase ÷= SimilarityBase +similarity_binop_assign_impl_all!( + MulAssign, mul_assign; + self: SimilarityBase, rhs: SimilarityBase; + [val] => *self *= &rhs; + [ref] => { + *self *= &rhs.isometry; + self.prepend_scaling_mut(rhs.scaling()); + }; +); + + +similarity_binop_assign_impl_all!( + DivAssign, div_assign; + self: SimilarityBase, rhs: SimilarityBase; + [val] => *self /= &rhs; + // FIXME: don't invert explicitly. + [ref] => *self *= rhs.inverse(); +); + + +// SimilarityBase ×= IsometryBase +// SimilarityBase ÷= IsometryBase +similarity_binop_assign_impl_all!( + MulAssign, mul_assign; + self: SimilarityBase, rhs: IsometryBase; + [val] => *self *= &rhs; + [ref] => { + let shift = self.isometry.rotation.transform_vector(&rhs.translation.vector) * self.scaling(); + self.isometry.translation.vector += shift; + self.isometry.rotation *= rhs.rotation.clone(); + }; +); + + +similarity_binop_assign_impl_all!( + DivAssign, div_assign; + self: SimilarityBase, rhs: IsometryBase; + [val] => *self /= &rhs; + // FIXME: don't invert explicitly. + [ref] => *self *= rhs.inverse(); +); + + +// SimilarityBase ×= R +// SimilarityBase ÷= R +similarity_binop_assign_impl_all!( + MulAssign, mul_assign; + self: SimilarityBase, rhs: R; + [val] => self.isometry.rotation *= rhs; + [ref] => self.isometry.rotation *= rhs.clone(); +); + + +similarity_binop_assign_impl_all!( + DivAssign, div_assign; + self: SimilarityBase, rhs: R; + // FIXME: don't invert explicitly? + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); + + +// SimilarityBase × R +// SimilarityBase ÷ R +similarity_binop_impl_all!( + Mul, mul; + self: SimilarityBase, rhs: R, Output = SimilarityBase; + [val val] => { + let scaling = self.scaling(); + SimilarityBase::from_isometry(self.isometry * rhs, scaling) + }; + [ref val] => SimilarityBase::from_isometry(&self.isometry * rhs, self.scaling()); + [val ref] => { + let scaling = self.scaling(); + SimilarityBase::from_isometry(self.isometry * rhs, scaling) + }; + [ref ref] => SimilarityBase::from_isometry(&self.isometry * rhs, self.scaling()); +); + + + +similarity_binop_impl_all!( + Div, div; + self: SimilarityBase, rhs: R, Output = SimilarityBase; + [val val] => { + let scaling = self.scaling(); + SimilarityBase::from_isometry(self.isometry / rhs, scaling) + }; + [ref val] => SimilarityBase::from_isometry(&self.isometry / rhs, self.scaling()); + [val ref] => { + let scaling = self.scaling(); + SimilarityBase::from_isometry(self.isometry / rhs, scaling) + }; + [ref ref] => SimilarityBase::from_isometry(&self.isometry / rhs, self.scaling()); +); + +// SimilarityBase × IsometryBase +// SimilarityBase ÷ IsometryBase +similarity_binop_impl_all!( + Mul, mul; + self: SimilarityBase, rhs: IsometryBase, Output = SimilarityBase; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let shift = self.isometry.rotation.transform_vector(&rhs.translation.vector) * self.scaling(); + SimilarityBase::from_parts( + TranslationBase::from_vector(&self.isometry.translation.vector + shift), + self.isometry.rotation.clone() * rhs.rotation.clone(), + self.scaling()) + }; +); + + + +similarity_binop_impl_all!( + Div, div; + self: SimilarityBase, rhs: IsometryBase, Output = SimilarityBase; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + +// IsometryBase × SimilarityBase +// IsometryBase ÷ SimilarityBase +similarity_binop_impl_all!( + Mul, mul; + self: IsometryBase, rhs: SimilarityBase, Output = SimilarityBase; + [val val] => { + let scaling = rhs.scaling(); + SimilarityBase::from_isometry(self * rhs.isometry, scaling) + }; + [ref val] => { + let scaling = rhs.scaling(); + SimilarityBase::from_isometry(self * rhs.isometry, scaling) + }; + [val ref] => { + let scaling = rhs.scaling(); + SimilarityBase::from_isometry(self * &rhs.isometry, scaling) + }; + [ref ref] => { + let scaling = rhs.scaling(); + SimilarityBase::from_isometry(self * &rhs.isometry, scaling) + }; +); + + +similarity_binop_impl_all!( + Div, div; + self: IsometryBase, rhs: SimilarityBase, Output = SimilarityBase; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + + +// SimilarityBase × PointBase +similarity_binop_impl_all!( + Mul, mul; + self: SimilarityBase, right: PointBase, Output = PointBase; + [val val] => { + let scaling = self.scaling(); + self.isometry.translation * (self.isometry.rotation.transform_point(&right) * scaling) + }; + [ref val] => &self.isometry.translation * (self.isometry.rotation.transform_point(&right) * self.scaling()); + [val ref] => { + let scaling = self.scaling(); + self.isometry.translation * (self.isometry.rotation.transform_point(right) * scaling) + }; + [ref ref] => &self.isometry.translation * (self.isometry.rotation.transform_point(right) * self.scaling()); +); + + +// SimilarityBase × Vector +similarity_binop_impl_all!( + Mul, mul; + self: SimilarityBase, right: ColumnVector, Output = ColumnVector; + [val val] => self.isometry.rotation.transform_vector(&right) * self.scaling(); + [ref val] => self.isometry.rotation.transform_vector(&right) * self.scaling(); + [val ref] => self.isometry.rotation.transform_vector(right) * self.scaling(); + [ref ref] => self.isometry.rotation.transform_vector(right) * self.scaling(); +); + + +// SimilarityBase × TranslationBase +similarity_binop_impl_all!( + Mul, mul; + self: SimilarityBase, right: TranslationBase, Output = SimilarityBase; + [val val] => &self * &right; + [ref val] => self * &right; + [val ref] => &self * right; + [ref ref] => { + let shift = self.isometry.rotation.transform_vector(&right.vector) * self.scaling(); + SimilarityBase::from_parts( + TranslationBase::from_vector(&self.isometry.translation.vector + shift), + self.isometry.rotation.clone(), + self.scaling()) + }; +); + + +// TranslationBase × SimilarityBase +similarity_binop_impl_all!( + Mul, mul; + self: TranslationBase, right: SimilarityBase, Output = SimilarityBase; + [val val] => { + let scaling = right.scaling(); + SimilarityBase::from_isometry(self * right.isometry, scaling) + }; + [ref val] => { + let scaling = right.scaling(); + SimilarityBase::from_isometry(self * right.isometry, scaling) + }; + [val ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); + [ref ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); +); + + +macro_rules! similarity_from_composition_impl( + ($Op: ident, $op: ident; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(for $Dims: ident: $DimsBound: ident),*; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs + where N: Real, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + type Output = $Output; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + +macro_rules! similarity_from_composition_impl_all( + ($Op: ident, $op: ident; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(for $Dims: ident: $DimsBound: ident),*; + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; + [val val] => $action_val_val: expr; + [ref val] => $action_ref_val: expr; + [val ref] => $action_val_ref: expr; + [ref ref] => $action_ref_ref: expr;) => { + + similarity_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: $Lhs, $rhs: $Rhs, Output = $Output; + $action_val_val; ); + + similarity_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Output; + $action_ref_val; 'a); + + similarity_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_val_ref; 'b); + + similarity_from_composition_impl!( + $Op, $op; + ($R1, $C1),($R2, $C2) $(for $Dims: $DimsBound),*; + $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Output; + $action_ref_ref; 'a, 'b); + } +); + + +// RotationBase × SimilarityBase +similarity_from_composition_impl_all!( + Mul, mul; + (D, D), (D, U1) for D: DimName; + self: RotationBase, right: SimilarityBase>, + Output = SimilarityBase>; + [val val] => &self * &right; + [ref val] => self * &right; + [val ref] => &self * right; + [ref ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); +); + + +// RotationBase ÷ SimilarityBase +similarity_from_composition_impl_all!( + Div, div; + (D, D), (D, U1) for D: DimName; + self: RotationBase, right: SimilarityBase>, + Output = SimilarityBase>; + // FIXME: don't call iverse explicitly? + [val val] => self * right.inverse(); + [ref val] => self * right.inverse(); + [val ref] => self * right.inverse(); + [ref ref] => self * right.inverse(); +); + + +// UnitQuaternion × SimilarityBase +similarity_from_composition_impl_all!( + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, right: SimilarityBase>, + Output = SimilarityBase>; + [val val] => &self * &right; + [ref val] => self * &right; + [val ref] => &self * right; + [ref ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); +); + + +// UnitQuaternion ÷ SimilarityBase +similarity_from_composition_impl_all!( + Div, div; + (U4, U1), (U3, U1); + self: UnitQuaternionBase, right: SimilarityBase>, + Output = SimilarityBase>; + // 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(); +); diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs new file mode 100644 index 00000000..831f6cab --- /dev/null +++ b/src/geometry/transform.rs @@ -0,0 +1,299 @@ +use std::any::Any; +use std::fmt::Debug; +use std::marker::PhantomData; +use approx::ApproxEq; + +use alga::general::Field; + +use core::{Scalar, SquareMatrix, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; + +/// Trait implemented by phantom types identifying the projective transformation type. +/// +/// NOTE: this trait is not intended to be implementable outside of the `nalgebra` crate. +pub trait TCategory: Any + Debug + Copy + PartialEq + Send { + #[inline] + fn has_normalizer() -> bool { + true + } + + /// Checks that the given matrix is a valid homogeneous representation of an element of the + /// category `Self`. + fn check_homogeneous_invariants(mat: &SquareMatrix) -> bool + where N: Scalar + Field + ApproxEq, + D: DimName, + S: Storage, + N::Epsilon: Copy; +} + +/// Traits that gives the transformation category that is compatible with the result of the +/// multiplication of transformations with categories `Self` and `Other`. +pub trait TCategoryMul: TCategory { + type Representative: TCategory; +} + +/// Indicates that `Self` is a more general transformation category than `Other`. +pub trait SuperTCategoryOf: TCategory { } + +/// Indicates that `Self` is a more specific transformation category than `Other`. +/// +/// Automatically implemented based on `SuperTCategoryOf`. +pub trait SubTCategoryOf: TCategory { } +impl SubTCategoryOf for T1 +where T1: TCategory, + T2: SuperTCategoryOf { +} + +/// Tag representing the most general (not necessarily inversible) transformation type. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct TGeneral; + +/// Tag representing the most general inversible transformation type. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct TProjective; + +/// Tag representing an affine transformation. Its bottom-row is equal to `(0, 0 ... 0, 1)`. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct TAffine; + +impl TCategory for TGeneral { + #[inline] + fn check_homogeneous_invariants(_: &SquareMatrix) -> bool + where N: Scalar + Field + ApproxEq, + D: DimName, + S: Storage, + N::Epsilon: Copy { + true + } +} + +impl TCategory for TProjective { + #[inline] + fn check_homogeneous_invariants(mat: &SquareMatrix) -> bool + where N: Scalar + Field + ApproxEq, + D: DimName, + S: Storage, + N::Epsilon: Copy { + mat.is_invertible() + } +} + +impl TCategory for TAffine { + #[inline] + fn has_normalizer() -> bool { + false + } + + #[inline] + fn check_homogeneous_invariants(mat: &SquareMatrix) -> bool + where N: Scalar + Field + ApproxEq, + D: DimName, + S: Storage, + N::Epsilon: Copy { + mat.is_invertible() && + mat[(D::dim(), D::dim())] == N::one() && + (0 .. D::dim()).all(|i| mat[(D::dim(), i)].is_zero()) + } +} + +macro_rules! category_mul_impl( + ($($a: ident * $b: ident => $c: ty);* $(;)*) => {$( + impl TCategoryMul<$a> for $b { + type Representative = $c; + } + )*} +); + +// We require stability uppon multiplication. +impl TCategoryMul for T { + type Representative = T; +} + +category_mul_impl!( + // TGeneral * TGeneral => TGeneral; + TGeneral * TProjective => TGeneral; + TGeneral * TAffine => TGeneral; + + TProjective * TGeneral => TGeneral; + // TProjective * TProjective => TProjective; + TProjective * TAffine => TProjective; + + TAffine * TGeneral => TGeneral; + TAffine * TProjective => TProjective; + // TAffine * TAffine => TAffine; +); + +macro_rules! super_tcategory_impl( + ($($a: ident >= $b: ident);* $(;)*) => {$( + impl SuperTCategoryOf<$b> for $a { } + )*} +); + +impl SuperTCategoryOf for T { } + +super_tcategory_impl!( + TGeneral >= TProjective; + TGeneral >= TAffine; + TProjective >= TAffine; +); + + +/// A transformation matrix that owns its data. +pub type OwnedTransform + = TransformBase, DimNameSum>>::Buffer, C>; + + +/// A transformation matrix in homogeneous coordinates. +/// +/// It is stored as a matrix with dimensions `(D + 1, D + 1)`, e.g., it stores a 4x4 matrix for a +/// 3D transformation. +#[repr(C)] +#[derive(Debug, Clone, Copy)] // FIXME: Hash +pub struct TransformBase, S, C: TCategory> { + matrix: SquareMatrix, S>, + _phantom: PhantomData +} + +// XXX: for some reasons, implementing Clone and Copy manually causes an ICE… + +impl Eq for TransformBase + where N: Scalar + Eq, + D: DimNameAdd, + S: Storage, DimNameSum> { } + +impl PartialEq for TransformBase + where N: Scalar, + D: DimNameAdd, + S: Storage, DimNameSum> { + #[inline] + fn eq(&self, right: &Self) -> bool { + self.matrix == right.matrix + } +} + +impl TransformBase + where N: Scalar, + D: DimNameAdd, + S: Storage, DimNameSum> { + /// Creates a new transformation from the given homogeneous matrix. The transformation category + /// of `Self` is not checked to be verified by the given matrix. + #[inline] + pub fn from_matrix_unchecked(matrix: SquareMatrix, S>) -> Self { + TransformBase { + matrix: matrix, + _phantom: PhantomData + } + } + + /// Moves this transform into one that owns its data. + #[inline] + pub fn into_owned(self) -> OwnedTransform { + TransformBase::from_matrix_unchecked(self.matrix.into_owned()) + } + + /// Clones this transform into one that owns its data. + #[inline] + pub fn clone_owned(&self) -> OwnedTransform { + TransformBase::from_matrix_unchecked(self.matrix.clone_owned()) + } + + /// The underlying matrix. + #[inline] + pub fn unwrap(self) -> SquareMatrix, S> { + self.matrix + } + + /// A reference to the underlynig matrix. + #[inline] + pub fn matrix(&self) -> &SquareMatrix, S> { + &self.matrix + } + + /// A mutable reference to the underlying matrix. + /// + /// It is `_unchecked` because direct modifications of this matrix may break invariants + /// identified by this transformation category. + #[inline] + pub fn matrix_mut_unchecked(&mut self) -> &mut SquareMatrix, S> { + &mut self.matrix + } + + /// Sets the category of this transform. + /// + /// This can be done only if the new category is more general than the current one, e.g., a + /// transform with category `TProjective` cannot be converted to a transform with category + /// `TAffine` because not all projective transformations are affine (the other way-round is + /// valid though). + #[inline] + pub fn set_category>(self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.matrix) + } + + /// Converts this transform into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> { + self.matrix().clone_owned() + } +} + +impl TransformBase + where N: Scalar + Field + ApproxEq, + D: DimNameAdd, + C: TCategory, + S: Storage, DimNameSum> { + /// Attempts to invert this transformation. You may use `.inverse` instead of this + /// transformation has a subcategory of `TProjective`. + #[inline] + pub fn try_inverse(self) -> Option> { + if let Some(m) = self.matrix.try_inverse() { + Some(TransformBase::from_matrix_unchecked(m)) + } + else { + None + } + } + + /// Inverts this transformation. Use `.try_inverse` if this transform has the `TGeneral` + /// category (it may not be invertible). + #[inline] + pub fn inverse(self) -> OwnedTransform + where C: SubTCategoryOf { + // FIXME: specialize for TAffine? + TransformBase::from_matrix_unchecked(self.matrix.try_inverse().unwrap()) + } +} + +impl TransformBase + where N: Scalar + Field + ApproxEq, + D: DimNameAdd, + C: TCategory, + S: StorageMut, DimNameSum> { + /// Attempts to invert this transformation in-place. You may use `.inverse_mut` instead of this + /// transformation has a subcategory of `TProjective`. + #[inline] + pub fn try_inverse_mut(&mut self) -> bool { + self.matrix.try_inverse_mut() + } + + /// Inverts this transformation in-place. Use `.try_inverse_mut` if this transform has the + /// `TGeneral` category (it may not be invertible). + #[inline] + pub fn inverse_mut(&mut self) + where C: SubTCategoryOf { + let _ = self.matrix.try_inverse_mut(); + } +} + +impl TransformBase + where N: Scalar, + D: DimNameAdd, + S: Storage, DimNameSum> { + /// A mutable reference to underlying matrix. Use `.matrix_mut_unchecked` instead if this + /// transformation category is not `TGeneral`. + #[inline] + pub fn matrix_mut(&mut self) -> &mut SquareMatrix, S> { + self.matrix_mut_unchecked() + } +} diff --git a/src/geometry/transform_alga.rs b/src/geometry/transform_alga.rs new file mode 100644 index 00000000..5c7e1abc --- /dev/null +++ b/src/geometry/transform_alga.rs @@ -0,0 +1,157 @@ +use approx::ApproxEq; + +use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, + AbstractSemigroup, Field, Real, Inverse, Multiplicative, Identity}; +use alga::linear::{Transformation, ProjectiveTransformation}; + +use core::{Scalar, ColumnVector}; +use core::dimension::{DimNameSum, DimNameAdd, U1}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; + +use geometry::{PointBase, TransformBase, TCategory, SubTCategoryOf, TProjective}; + + +/* + * + * Algebraic structures. + * + */ +impl, S, C> Identity for TransformBase + where N: Scalar + Field, + S: OwnedStorage, DimNameSum>, + C: TCategory, + S::Alloc: OwnedAllocator, DimNameSum, S> { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl, S, C> Inverse for TransformBase + where N: Scalar + Field + ApproxEq, + S: OwnedStorage, DimNameSum>, + C: SubTCategoryOf, + S::Alloc: OwnedAllocator, DimNameSum, S> { + #[inline] + fn inverse(&self) -> Self { + self.clone().inverse() + } + + #[inline] + fn inverse_mut(&mut self) { + self.inverse_mut() + } +} + +impl, S, C> AbstractMagma for TransformBase + where N: Scalar + Field, + S: OwnedStorage, DimNameSum>, + C: TCategory, + S::Alloc: OwnedAllocator, DimNameSum, S> { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +macro_rules! impl_multiplicative_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl, S, C> $marker<$operator> for TransformBase + where N: Scalar + Field, + S: OwnedStorage, DimNameSum>, + C: TCategory, + S::Alloc: OwnedAllocator, DimNameSum, S> { } + )*} +); + +macro_rules! impl_inversible_multiplicative_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl, S, C> $marker<$operator> for TransformBase + where N: Scalar + Field + ApproxEq, + S: OwnedStorage, DimNameSum>, + C: SubTCategoryOf, + S::Alloc: OwnedAllocator, DimNameSum, S> { } + )*} +); + +impl_multiplicative_structures!( + AbstractSemigroup, + AbstractMonoid, +); + +impl_inversible_multiplicative_structures!( + AbstractQuasigroup, + AbstractLoop, + AbstractGroup +); + +/* + * + * Transformation groups. + * + */ +impl, SA, SB, C> Transformation> for TransformBase + where N: Real, + SA: OwnedStorage, DimNameSum>, + SB: OwnedStorage, + C: TCategory, + SA::Alloc: OwnedAllocator, DimNameSum, SA> + + Allocator + + Allocator + + Allocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + self * pt + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self * v + } +} + +impl, SA, SB, C> ProjectiveTransformation> for TransformBase + where N: Real, + SA: OwnedStorage, DimNameSum>, + SB: OwnedStorage, + C: SubTCategoryOf, + SA::Alloc: OwnedAllocator, DimNameSum, SA> + + Allocator + + Allocator + + Allocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + self.inverse() * pt + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self.inverse() * v + } +} + +// FIXME: we need to implement an SVD for this. +// +// impl, SA, SB, C> AffineTransformation> for TransformBase +// where N: Real, +// SA: OwnedStorage, DimNameSum>, +// SB: OwnedStorage, +// C: SubTCategoryOf, +// SA::Alloc: OwnedAllocator, DimNameSum, SA> + +// Allocator + +// Allocator + +// Allocator, +// SB::Alloc: OwnedAllocator { +// type PreRotation = OwnedRotation; +// type NonUniformScaling = OwnedColumnVector; +// type PostRotation = OwnedRotation; +// type Translation = OwnedTranslation; +// +// #[inline] +// fn decompose(&self) -> (Self::Translation, Self::PostRotation, Self::NonUniformScaling, Self::PreRotation) { +// unimplemented!() +// } +// } diff --git a/src/geometry/transform_alias.rs b/src/geometry/transform_alias.rs new file mode 100644 index 00000000..1919916c --- /dev/null +++ b/src/geometry/transform_alias.rs @@ -0,0 +1,16 @@ +use core::MatrixArray; +use core::dimension::{U1, U2, U3, DimNameSum}; + +use geometry::{TransformBase, TGeneral, TProjective, TAffine}; + +pub type Transform = TransformBase, DimNameSum>, TGeneral>; +pub type Projective = TransformBase, DimNameSum>, TProjective>; +pub type Affine = TransformBase, DimNameSum>, TAffine>; + +pub type Transform2 = Transform; +pub type Projective2 = Projective; +pub type Affine2 = Affine; + +pub type Transform3 = Transform; +pub type Projective3 = Projective; +pub type Affine3 = Affine; diff --git a/src/geometry/transform_construction.rs b/src/geometry/transform_construction.rs new file mode 100644 index 00000000..9a2dbe98 --- /dev/null +++ b/src/geometry/transform_construction.rs @@ -0,0 +1,36 @@ +use num::{Zero, One}; + +use alga::general::Field; + +use core::{Scalar, OwnedSquareMatrix}; +use core::dimension::{DimNameAdd, DimNameSum, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{TransformBase, TCategory}; + + +impl TransformBase + where N: Scalar + Zero + One, + D: DimNameAdd, + S: OwnedStorage, DimNameSum>, + S::Alloc: OwnedAllocator, DimNameSum, S> { + /// Creates a new identity transform. + #[inline] + pub fn identity() -> Self { + Self::from_matrix_unchecked(OwnedSquareMatrix::::identity()) + } +} + +impl One for TransformBase + where N: Scalar + Field, + D: DimNameAdd, + S: OwnedStorage, DimNameSum>, + S::Alloc: OwnedAllocator, DimNameSum, S> { + /// Creates a new identity transform. + #[inline] + fn one() -> Self { + Self::identity() + } +} + diff --git a/src/geometry/transform_conversion.rs b/src/geometry/transform_conversion.rs new file mode 100644 index 00000000..44267a00 --- /dev/null +++ b/src/geometry/transform_conversion.rs @@ -0,0 +1,67 @@ +use approx::ApproxEq; + +use alga::general::{SubsetOf, Field}; + +use core::{Scalar, SquareMatrix}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{TransformBase, TCategory, SuperTCategoryOf}; + + +impl SubsetOf> for TransformBase + where N1: Scalar + Field + ApproxEq + SubsetOf, + N2: Scalar + Field + ApproxEq, + C1: TCategory, + C2: SuperTCategoryOf, + D: DimNameAdd, + SA: OwnedStorage, DimNameSum>, + SB: OwnedStorage, DimNameSum>, + SA::Alloc: OwnedAllocator, DimNameSum, SA>, + SB::Alloc: OwnedAllocator, DimNameSum, SB>, + N1::Epsilon: Copy, + N2::Epsilon: Copy { + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf, SB>> for TransformBase + where N1: Scalar + Field + ApproxEq + SubsetOf, + N2: Scalar + Field + ApproxEq, + C: TCategory, + D: DimNameAdd, + SA: OwnedStorage, DimNameSum>, + SB: OwnedStorage, DimNameSum>, + SA::Alloc: OwnedAllocator, DimNameSum, SA>, + SB::Alloc: OwnedAllocator, DimNameSum, SB>, + N1::Epsilon: Copy, + N2::Epsilon: Copy { + #[inline] + fn to_superset(&self) -> SquareMatrix, SB> { + self.matrix().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + C::check_homogeneous_invariants(m) + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + TransformBase::from_matrix_unchecked(::convert_ref_unchecked(m)) + } +} diff --git a/src/geometry/transform_ops.rs b/src/geometry/transform_ops.rs new file mode 100644 index 00000000..21a8cf81 --- /dev/null +++ b/src/geometry/transform_ops.rs @@ -0,0 +1,608 @@ +use num::{Zero, One}; +use std::ops::{Index, IndexMut, Mul, MulAssign, Div, DivAssign}; +use approx::ApproxEq; + +use alga::general::{Field, Real, ClosedAdd, ClosedMul, ClosedNeg, SubsetOf}; + +use core::{Scalar, ColumnVector, OwnedColumnVector, OwnedSquareMatrix}; +use core::storage::{Storage, StorageMut, OwnedStorage}; +use core::allocator::{Allocator, OwnedAllocator}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1, U3, U4}; + +use geometry::{PointBase, OwnedPoint, TransformBase, OwnedTransform, TCategory, TCategoryMul, + SubTCategoryOf, SuperTCategoryOf, TGeneral, TProjective, TAffine, RotationBase, + UnitQuaternionBase, IsometryBase, SimilarityBase, TranslationBase}; + +/* + * + * Indexing. + * + */ +impl Index<(usize, usize)> for TransformBase + where N: Scalar, + D: DimName + DimNameAdd, + S: Storage, DimNameSum> { + type Output = N; + + #[inline] + fn index(&self, ij: (usize, usize)) -> &N { + self.matrix().index(ij) + } +} + +// Only general transformations are mutably indexable. +impl IndexMut<(usize, usize)> for TransformBase + where N: Scalar, + D: DimName + DimNameAdd, + S: StorageMut, DimNameSum> { + #[inline] + fn index_mut(&mut self, ij: (usize, usize)) -> &mut N { + self.matrix_mut().index_mut(ij) + } +} + +/* + * + * In the following, we provide: + * ========================= + * + * + * (Operators) + * + * [OK] TransformBase × IsometryBase + * [OK] TransformBase × RotationBase + * [OK] TransformBase × SimilarityBase + * [OK] TransformBase × TransformBase + * [OK] TransformBase × UnitQuaternion + * [OK] TransformBase × TranslationBase + * [OK] TransformBase × ColumnVector + * [OK] TransformBase × PointBase + * + * [OK] IsometryBase × TransformBase + * [OK] RotationBase × TransformBase + * [OK] SimilarityBase × TransformBase + * [OK] TranslationBase × TransformBase + * [OK] UnitQuaternionBase × TransformBase + * + * TransformBase ÷ IsometryBase + * [OK] TransformBase ÷ RotationBase + * TransformBase ÷ SimilarityBase + * [OK] TransformBase ÷ TransformBase + * [OK] TransformBase ÷ UnitQuaternion + * [OK] TransformBase ÷ TranslationBase + * + * IsometryBase ÷ TransformBase + * [OK] RotationBase ÷ TransformBase + * SimilarityBase ÷ TransformBase + * [OK] TranslationBase ÷ TransformBase + * [OK] UnitQuaternionBase ÷ TransformBase + * + * + * (Assignment Operators) + * + * + * [OK] TransformBase ×= TransformBase + * [OK] TransformBase ×= SimilarityBase + * [OK] TransformBase ×= IsometryBase + * [OK] TransformBase ×= RotationBase + * [OK] TransformBase ×= UnitQuaternionBase + * [OK] TransformBase ×= TranslationBase + * + * [OK] TransformBase ÷= TransformBase + * TransformBase ÷= SimilarityBase + * TransformBase ÷= IsometryBase + * [OK] TransformBase ÷= RotationBase + * [OK] TransformBase ÷= UnitQuaternionBase + * + */ + + +// TransformBase × ColumnVector +md_impl_all!( + Mul, mul where N: Field; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory + where SA::Alloc: Allocator + where SA::Alloc: Allocator + where SA::Alloc: Allocator; + self: TransformBase, rhs: ColumnVector, Output = OwnedColumnVector; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let transform = self.matrix().fixed_slice::(0, 0); + + if C::has_normalizer() { + let normalizer = self.matrix().fixed_slice::(D::dim(), 0); + let n = normalizer.tr_dot(&rhs); + + if !n.is_zero() { + return transform * (rhs / n); + } + } + + transform * rhs + }; +); + + +// TransformBase × PointBase +md_impl_all!( + Mul, mul where N: Field; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory + where SA::Alloc: Allocator + where SA::Alloc: Allocator + where SA::Alloc: Allocator; + self: TransformBase, rhs: PointBase, Output = OwnedPoint; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let transform = self.matrix().fixed_slice::(0, 0); + let translation = self.matrix().fixed_slice::(0, D::dim()); + + if C::has_normalizer() { + let normalizer = self.matrix().fixed_slice::(D::dim(), 0); + let n = normalizer.tr_dot(&rhs.coords) + unsafe { *self.matrix().get_unchecked(D::dim(), D::dim()) }; + + if !n.is_zero() { + return transform * (rhs / n) + translation; + } + } + + transform * rhs + translation + }; +); + + +// TransformBase × TransformBase +md_impl_all!( + Mul, mul; + (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategoryMul, CB: TCategory; + self: TransformBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.unwrap()); + [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.unwrap()); + [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.matrix()); + [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.matrix()); +); + + +// TransformBase × RotationBase +md_impl_all!( + Mul, mul where N: One; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategoryMul + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: RotationBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); + [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +); + + +// RotationBase × TransformBase +md_impl_all!( + Mul, mul where N: One; + (D, D), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul + where SA::Alloc: Allocator, DimNameSum >; + self: RotationBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); + [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +); + + +// TransformBase × UnitQuaternionBase +md_impl_all!( + Mul, mul where N: Real; + (U4, U4), (U4, U1) for C: TCategoryMul + where SB::Alloc: Allocator + where SB::Alloc: Allocator; + self: TransformBase, rhs: UnitQuaternionBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); + [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +); + + +// UnitQuaternionBase × TransformBase +md_impl_all!( + Mul, mul where N: Real; + (U4, U1), (U4, U4) for C: TCategoryMul + where SA::Alloc: Allocator + where SA::Alloc: Allocator; + self: UnitQuaternionBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); + [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +); + + + +// TransformBase × IsometryBase +md_impl_all!( + Mul, mul where N: Real; + (DimNameSum, DimNameSum), (D, U1) + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: IsometryBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); + [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +); + +// IsometryBase × TransformBase +md_impl_all!( + Mul, mul where N: Real; + (D, U1), (DimNameSum, DimNameSum) + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > + where SA::Alloc: Allocator, DimNameSum >; + self: IsometryBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); + [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +); + + +// TransformBase × SimilarityBase +md_impl_all!( + Mul, mul where N: Real; + (DimNameSum, DimNameSum), (D, U1) + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > + where SB::Alloc: Allocator + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: SimilarityBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); + [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +); + +// SimilarityBase × TransformBase +md_impl_all!( + Mul, mul where N: Real; + (D, U1), (DimNameSum, DimNameSum) + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > + where SA::Alloc: Allocator + where SA::Alloc: Allocator, DimNameSum >; + self: SimilarityBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); + [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +); + + + +/* + * + * FIXME: don't explicitly build the homogeneous translation matrix. + * Directly apply the translation, just as in `Matrix::{append,prepend}_translation`. This has not + * been done yet because of the `DimNameDiff` requirement (which is not automatically deduced from + * `DimNameAdd` requirement). + * + */ +// TransformBase × TranslationBase +md_impl_all!( + Mul, mul where N: Real; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategoryMul + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: TranslationBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); + [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); + [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +); + +// TranslationBase × TransformBase +md_impl_all!( + Mul, mul where N: Real; + (D, U1), (DimNameSum, DimNameSum) + for D: DimNameAdd, C: TCategoryMul + where SA::Alloc: Allocator, DimNameSum >; + self: TranslationBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); + [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); + [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +); + + + +// TransformBase ÷ TransformBase +md_impl_all!( + Div, div where N: ApproxEq, Field; + (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategoryMul, CB: SubTCategoryOf; + self: TransformBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.clone_owned().inverse(); + [ref ref] => self * rhs.clone_owned().inverse(); +); + +// TransformBase ÷ RotationBase +md_impl_all!( + Div, div where N: One; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategoryMul + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: RotationBase, Output = OwnedTransform; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + + +// RotationBase ÷ TransformBase +md_impl_all!( + Div, div where N: One; + (D, D), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul + where SA::Alloc: Allocator, DimNameSum >; + self: RotationBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => self.inverse() * rhs; + [ref val] => self.inverse() * rhs; + [val ref] => self.inverse() * rhs; + [ref ref] => self.inverse() * rhs; +); + + +// TransformBase ÷ UnitQuaternionBase +md_impl_all!( + Div, div where N: Real; + (U4, U4), (U4, U1) for C: TCategoryMul + where SB::Alloc: Allocator + where SB::Alloc: Allocator; + self: TransformBase, rhs: UnitQuaternionBase, Output = OwnedTransform; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + + +// UnitQuaternionBase ÷ TransformBase +md_impl_all!( + Div, div where N: Real; + (U4, U1), (U4, U4) for C: TCategoryMul + where SA::Alloc: Allocator + where SA::Alloc: Allocator; + self: UnitQuaternionBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => self.inverse() * rhs; + [ref val] => self.inverse() * rhs; + [val ref] => self.inverse() * rhs; + [ref ref] => self.inverse() * rhs; +); + + + +// // TransformBase ÷ IsometryBase +// md_impl_all!( +// Div, div where N: Real; +// (DimNameSum, DimNameSum), (D, U1) +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > +// where SB::Alloc: Allocator, DimNameSum >; +// self: TransformBase, rhs: IsometryBase, Output = OwnedTransform; +// [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.inverse().to_homogeneous()); +// [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.inverse().to_homogeneous()); +// [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.inverse().to_homogeneous()); +// [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.inverse().to_homogeneous()); +// ); + +// // IsometryBase ÷ TransformBase +// md_impl_all!( +// Div, div where N: Real; +// (D, U1), (DimNameSum, DimNameSum) +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > +// where SA::Alloc: Allocator, DimNameSum >; +// self: IsometryBase, rhs: TransformBase, Output = OwnedTransform; +// [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); +// [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); +// [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +// [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +// ); + + +// // TransformBase ÷ SimilarityBase +// md_impl_all!( +// Div, div where N: Real; +// (DimNameSum, DimNameSum), (D, U1) +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > +// where SB::Alloc: Allocator +// where SB::Alloc: Allocator, DimNameSum >; +// self: TransformBase, rhs: SimilarityBase, Output = OwnedTransform; +// [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); +// [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +// [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); +// [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); +// ); + +// // SimilarityBase ÷ TransformBase +// md_impl_all!( +// Div, div where N: Real; +// (D, U1), (DimNameSum, DimNameSum) +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > +// where SA::Alloc: Allocator +// where SA::Alloc: Allocator, DimNameSum >; +// self: SimilarityBase, rhs: TransformBase, Output = OwnedTransform; +// [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); +// [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); +// [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +// [ref ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); +// ); + + + +// TransformBase ÷ TranslationBase +md_impl_all!( + Div, div where N: Real; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategoryMul + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: TranslationBase, Output = OwnedTransform; + [val val] => self * rhs.inverse(); + [ref val] => self * rhs.inverse(); + [val ref] => self * rhs.inverse(); + [ref ref] => self * rhs.inverse(); +); + +// TranslationBase ÷ TransformBase +md_impl_all!( + Div, div where N: Real; + (D, U1), (DimNameSum, DimNameSum) + for D: DimNameAdd, C: TCategoryMul + where SA::Alloc: Allocator, DimNameSum >; + self: TranslationBase, rhs: TransformBase, Output = OwnedTransform; + [val val] => self.inverse() * rhs; + [ref val] => self.inverse() * rhs; + [val ref] => self.inverse() * rhs; + [ref ref] => self.inverse() * rhs; +); + + +// TransformBase ×= TransformBase +md_assign_impl_all!( + MulAssign, mul_assign; + (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategory, CB: SubTCategoryOf; + self: TransformBase, rhs: TransformBase; + [val] => *self.matrix_mut_unchecked() *= rhs.unwrap(); + [ref] => *self.matrix_mut_unchecked() *= rhs.matrix(); +); + + +// TransformBase ×= SimilarityBase +md_assign_impl_all!( + MulAssign, mul_assign; + (DimNameSum, DimNameSum), (D, U1) + for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > + where SB::Alloc: Allocator, DimNameSum > + where SB::Alloc: Allocator; + self: TransformBase, rhs: SimilarityBase; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); +); + + +// TransformBase ×= IsometryBase +md_assign_impl_all!( + MulAssign, mul_assign; + (DimNameSum, DimNameSum), (D, U1) + for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: IsometryBase; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); +); + +/* + * + * FIXME: don't explicitly build the homogeneous translation matrix. + * Directly apply the translation, just as in `Matrix::{append,prepend}_translation`. This has not + * been done yet because of the `DimNameDiff` requirement (which is not automatically deduced from + * `DimNameAdd` requirement). + * + */ +// TransformBase ×= TranslationBase +md_assign_impl_all!( + MulAssign, mul_assign where N: One; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: TranslationBase; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); +); + + +// TransformBase ×= RotationBase +md_assign_impl_all!( + MulAssign, mul_assign where N: One; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategory + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: RotationBase; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); +); + + +// TransformBase ×= UnitQuaternionBase +md_assign_impl_all!( + MulAssign, mul_assign where N: Real; + (U4, U4), (U4, U1) for C: TCategory + where SB::Alloc: Allocator + where SB::Alloc: Allocator; + self: TransformBase, rhs: UnitQuaternionBase; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); +); + + +// TransformBase ÷= TransformBase +md_assign_impl_all!( + DivAssign, div_assign where N: Field, ApproxEq; + (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) + for D: DimNameAdd, CA: SuperTCategoryOf, CB: SubTCategoryOf; + self: TransformBase, rhs: TransformBase; + [val] => *self *= rhs.clone_owned().inverse(); + [ref] => *self *= rhs.clone_owned().inverse(); +); + + +// // TransformBase ÷= SimilarityBase +// md_assign_impl_all!( +// DivAssign, div_assign; +// (DimNameSum, DimNameSum), (D, U1) +// for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > +// where SB::Alloc: Allocator, DimNameSum > +// where SB::Alloc: Allocator; +// self: TransformBase, rhs: SimilarityBase; +// [val] => *self *= rhs.inverse(); +// [ref] => *self *= rhs.inverse(); +// ); +// +// +// // TransformBase ÷= IsometryBase +// md_assign_impl_all!( +// DivAssign, div_assign; +// (DimNameSum, DimNameSum), (D, U1) +// for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > +// where SB::Alloc: Allocator, DimNameSum >; +// self: TransformBase, rhs: IsometryBase; +// [val] => *self *= rhs.inverse(); +// [ref] => *self *= rhs.inverse(); +// ); + + +// TransformBase ÷= TranslationBase +md_assign_impl_all!( + DivAssign, div_assign where N: One, ClosedNeg; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: TranslationBase; + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); + + +// TransformBase ÷= RotationBase +md_assign_impl_all!( + DivAssign, div_assign where N: One; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategory + where SB::Alloc: Allocator, DimNameSum >; + self: TransformBase, rhs: RotationBase; + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); + + +// TransformBase ÷= UnitQuaternionBase +md_assign_impl_all!( + DivAssign, div_assign where N: Real; + (U4, U4), (U4, U1) for C: TCategory + where SB::Alloc: Allocator + where SB::Alloc: Allocator; + self: TransformBase, rhs: UnitQuaternionBase; + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs new file mode 100644 index 00000000..182ca2b0 --- /dev/null +++ b/src/geometry/translation.rs @@ -0,0 +1,142 @@ +use num::{Zero, One}; +use std::fmt; +use approx::ApproxEq; + +use alga::general::{Real, ClosedNeg}; + +use core::{Scalar, ColumnVector, OwnedSquareMatrix}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::{Storage, StorageMut, Owned}; +use core::allocator::Allocator; + +/// A translation with an owned vector storage. +pub type OwnedTranslation = TranslationBase>::Alloc>>; + +/// A translation. +#[repr(C)] +#[derive(Hash, Debug, Clone, Copy)] +pub struct TranslationBase*/> { + pub vector: ColumnVector +} + +impl TranslationBase + where N: Scalar, + S: Storage { + /// Creates a new translation from the given vector. + #[inline] + pub fn from_vector(vector: ColumnVector) -> TranslationBase { + TranslationBase { + vector: vector + } + } + + /// Inverts `self`. + #[inline] + pub fn inverse(&self) -> OwnedTranslation + where N: ClosedNeg { + TranslationBase::from_vector(-&self.vector) + } + + /// Converts this translation into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + where N: Zero + One, + D: DimNameAdd, + S::Alloc: Allocator, DimNameSum> { + let mut res = OwnedSquareMatrix::::identity(); + res.fixed_slice_mut::(0, D::dim()).copy_from(&self.vector); + + res + } +} + + +impl TranslationBase + where N: Scalar + ClosedNeg, + S: StorageMut { + /// Inverts `self` in-place. + #[inline] + pub fn inverse_mut(&mut self) { + self.vector.neg_mut() + } +} + +impl Eq for TranslationBase + where N: Scalar + Eq, + S: Storage { +} + +impl PartialEq for TranslationBase + where N: Scalar + PartialEq, + S: Storage { + #[inline] + fn eq(&self, right: &TranslationBase) -> bool { + self.vector == right.vector + } +} + +impl ApproxEq for TranslationBase + where N: Scalar + ApproxEq, + S: Storage, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.vector.relative_eq(&other.vector, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.vector.ulps_eq(&other.vector, epsilon, max_ulps) + } +} + +/* + * + * Display + * + */ +impl fmt::Display for TranslationBase + where N: Real + fmt::Display, + S: Storage, + S::Alloc: Allocator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let precision = f.precision().unwrap_or(3); + + try!(writeln!(f, "TranslationBase {{")); + try!(write!(f, "{:.*}", precision, self.vector)); + writeln!(f, "}}") + } +} + + +// // /* +// // * +// // * Absolute +// // * +// // */ +// // impl Absolute for $t { +// // type AbsoluteValue = $submatrix; +// // +// // #[inline] +// // fn abs(m: &$t) -> $submatrix { +// // Absolute::abs(&m.submatrix) +// // } +// // } +// */ diff --git a/src/geometry/translation_alga.rs b/src/geometry/translation_alga.rs new file mode 100644 index 00000000..fcac3860 --- /dev/null +++ b/src/geometry/translation_alga.rs @@ -0,0 +1,209 @@ +use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, + AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; +use alga::linear::{Transformation, ProjectiveTransformation, Similarity, AffineTransformation, + Isometry, DirectIsometry, Translation}; + +use core::ColumnVector; +use core::dimension::{DimName, U1}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::{TranslationBase, PointBase}; + + +/* + * + * Algebraic structures. + * + */ +impl Identity for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl Inverse for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse(&self) -> Self { + self.inverse() + } + + #[inline] + fn inverse_mut(&mut self) { + self.inverse_mut() + } +} + +impl AbstractMagma for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +macro_rules! impl_multiplicative_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl $marker<$operator> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +impl_multiplicative_structures!( + AbstractSemigroup, + AbstractMonoid, + AbstractQuasigroup, + AbstractLoop, + AbstractGroup +); + +/* + * + * Transformation groups. + * + */ +impl Transformation> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + pt + &self.vector + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + v.clone() + } +} + +impl ProjectiveTransformation> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + pt - &self.vector + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + v.clone() + } +} + +impl AffineTransformation> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Rotation = Id; + type NonUniformScaling = Id; + type Translation = Self; + + #[inline] + fn decompose(&self) -> (Self, Id, Id, Id) { + (self.clone(), Id::new(), Id::new(), Id::new()) + } + + #[inline] + fn append_translation(&self, t: &Self::Translation) -> Self { + t * self + } + + #[inline] + fn prepend_translation(&self, t: &Self::Translation) -> Self { + self * t + } + + #[inline] + fn append_rotation(&self, _: &Self::Rotation) -> Self { + self.clone() + } + + #[inline] + fn prepend_rotation(&self, _: &Self::Rotation) -> Self { + self.clone() + } + + #[inline] + fn append_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } + + #[inline] + fn prepend_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } +} + + +impl Similarity> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Scaling = Id; + + #[inline] + fn translation(&self) -> Self { + self.clone() + } + + #[inline] + fn rotation(&self) -> Id { + Id::new() + } + + #[inline] + fn scaling(&self) -> Id { + Id::new() + } +} + +macro_rules! marker_impl( + ($($Trait: ident),*) => {$( + impl $Trait> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +marker_impl!(Isometry, DirectIsometry); + + +/// Subgroups of the n-dimensional translation group `T(n)`. +impl Translation> for TranslationBase + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn to_vector(&self) -> ColumnVector { + self.vector.clone() + } + + #[inline] + fn from_vector(v: ColumnVector) -> Option { + Some(Self::from_vector(v)) + } + + #[inline] + fn powf(&self, n: N) -> Option { + Some(Self::from_vector(&self.vector * n)) + } + + #[inline] + fn translation_between(a: &PointBase, b: &PointBase) -> Option { + Some(Self::from_vector(b - a)) + } +} diff --git a/src/geometry/translation_alias.rs b/src/geometry/translation_alias.rs new file mode 100644 index 00000000..b607d6af --- /dev/null +++ b/src/geometry/translation_alias.rs @@ -0,0 +1,13 @@ +use core::MatrixArray; +use core::dimension::{U1, U2, U3}; + +use geometry::TranslationBase; + +/// A D-dimensional translation. +pub type Translation = TranslationBase>; + +/// A 2-dimensional translation. +pub type Translation2 = Translation; + +/// A 3-dimensional translation. +pub type Translation3 = Translation; diff --git a/src/geometry/translation_construction.rs b/src/geometry/translation_construction.rs new file mode 100644 index 00000000..92bbe342 --- /dev/null +++ b/src/geometry/translation_construction.rs @@ -0,0 +1,86 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use num::{Zero, One}; +use rand::{Rng, Rand}; + +use alga::general::ClosedAdd; + +use core::{ColumnVector, Scalar}; +use core::dimension::{DimName, U1, U2, U3, U4, U5, U6}; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; + +use geometry::TranslationBase; + +impl TranslationBase + where N: Scalar + Zero, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new square identity rotation of the given `dimension`. + #[inline] + pub fn identity() -> TranslationBase { + Self::from_vector(ColumnVector::::from_element(N::zero())) + } +} + +impl One for TranslationBase + where N: Scalar + Zero + ClosedAdd, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Rand for TranslationBase + where N: Scalar + Rand, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn rand(rng: &mut G) -> Self { + Self::from_vector(rng.gen()) + } +} + + +#[cfg(feature = "arbitrary")] +impl Arbitrary for TranslationBase + where N: Scalar + Arbitrary + Send, + S: OwnedStorage + Send, + S::Alloc: OwnedAllocator { + #[inline] + fn arbitrary(rng: &mut G) -> Self { + Self::from_vector(Arbitrary::arbitrary(rng)) + } +} + +/* + * + * Small translation construction from components. + * + */ +macro_rules! componentwise_constructors_impl( + ($($D: ty, $($args: ident:$irow: expr),*);* $(;)*) => {$( + impl TranslationBase + where N: Scalar, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Initializes this matrix from its components. + #[inline] + pub fn new($($args: N),*) -> Self { + Self::from_vector(ColumnVector::::new($($args),*)) + } + } + )*} +); + +componentwise_constructors_impl!( + U1, x:0; + U2, x:0, y:1; + U3, x:0, y:1, z:2; + U4, x:0, y:1, z:2, w:3; + U5, x:0, y:1, z:2, w:3, a:4; + U6, x:0, y:1, z:2, w:3, a:4, b:5; +); diff --git a/src/geometry/translation_conversion.rs b/src/geometry/translation_conversion.rs new file mode 100644 index 00000000..c449a54f --- /dev/null +++ b/src/geometry/translation_conversion.rs @@ -0,0 +1,159 @@ +use alga::general::{SubsetOf, SupersetOf, Real}; +use alga::linear::Rotation; + +use core::{Scalar, ColumnVector, SquareMatrix}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; + +use geometry::{PointBase, TranslationBase, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; + +/* + * This file provides the following conversions: + * ============================================= + * + * TranslationBase -> TranslationBase + * TranslationBase -> IsometryBase + * TranslationBase -> SimilarityBase + * TranslationBase -> TransformBase + * TranslationBase -> Matrix (homogeneous) + */ + +impl SubsetOf> for TranslationBase + where N1: Scalar, + N2: Scalar + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> TranslationBase { + TranslationBase::from_vector(self.vector.to_superset()) + } + + #[inline] + fn is_in_subset(rot: &TranslationBase) -> bool { + ::is_convertible::<_, ColumnVector>(&rot.vector) + } + + #[inline] + unsafe fn from_superset_unchecked(rot: &TranslationBase) -> Self { + TranslationBase::from_vector(rot.vector.to_subset_unchecked()) + } +} + + +impl SubsetOf> for TranslationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + R: Rotation>, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> IsometryBase { + IsometryBase::from_parts(self.to_superset(), R::identity()) + } + + #[inline] + fn is_in_subset(iso: &IsometryBase) -> bool { + iso.rotation == R::identity() + } + + #[inline] + unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + Self::from_superset_unchecked(&iso.translation) + } +} + + +impl SubsetOf> for TranslationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, + R: Rotation>, + SA::Alloc: OwnedAllocator, + SB::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> SimilarityBase { + SimilarityBase::from_parts(self.to_superset(), R::identity(), N2::one()) + } + + #[inline] + fn is_in_subset(sim: &SimilarityBase) -> bool { + sim.isometry.rotation == R::identity() && + sim.scaling() == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + Self::from_superset_unchecked(&sim.isometry.translation) + } +} + + +impl SubsetOf> for TranslationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + C: SuperTCategoryOf, + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator, DimNameSum>, + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + + Allocator, D> { + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf, SB>> for TranslationBase + where N1: Real, + N2: Real + SupersetOf, + SA: OwnedStorage, + SB: OwnedStorage, DimNameSum>, + D: DimNameAdd, + SA::Alloc: OwnedAllocator + + Allocator, DimNameSum>, + SB::Alloc: OwnedAllocator, DimNameSum, SB> + + Allocator + + Allocator, D> { + #[inline] + fn to_superset(&self) -> SquareMatrix, SB> { + self.to_homogeneous().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + let id = m.fixed_slice::, D>(0, 0); + + // Scalar types agree. + m.iter().all(|e| SupersetOf::::is_in_subset(e)) && + // The block part does nothing. + id.is_identity(N2::zero()) && + // The normalization factor is one. + m[(D::dim(), D::dim())] == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + let t = m.fixed_slice::(0, D::dim()); + Self::from_vector(::convert_unchecked(t.into_owned())) + } +} diff --git a/src/geometry/translation_ops.rs b/src/geometry/translation_ops.rs new file mode 100644 index 00000000..08db5ac6 --- /dev/null +++ b/src/geometry/translation_ops.rs @@ -0,0 +1,168 @@ +use std::ops::{Mul, MulAssign, Div, DivAssign}; + +use alga::general::{ClosedAdd, ClosedSub}; + +use core::Scalar; +use core::dimension::{DimName, U1}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; +use core::storage::{OwnedStorage, Storage}; +use core::allocator::{OwnedAllocator, SameShapeAllocator}; + +use geometry::{PointBase, OwnedPoint, TranslationBase, OwnedTranslation}; + +// TranslationBase × TranslationBase +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: &'a TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(&self.vector + &right.vector); 'a, 'b); + +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: &'a TranslationBase, right: TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(&self.vector + right.vector); 'a); + +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(self.vector + &right.vector); 'b); + +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: TranslationBase, right: TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(self.vector + right.vector); ); + +// TranslationBase ÷ TranslationBase +// FIXME: instead of calling inverse explicitely, could we just add a `mul_tr` or `mul_inv` method? +add_sub_impl!(Div, div, ClosedSub; + (D, U1), (D, U1) -> (D) for D: DimName; + self: &'a TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(&self.vector - &right.vector); 'a, 'b); + +add_sub_impl!(Div, div, ClosedSub; + (D, U1), (D, U1) -> (D) for D: DimName; + self: &'a TranslationBase, right: TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(&self.vector - right.vector); 'a); + +add_sub_impl!(Div, div, ClosedSub; + (D, U1), (D, U1) -> (D) for D: DimName; + self: TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(self.vector - &right.vector); 'b); + +add_sub_impl!(Div, div, ClosedSub; + (D, U1), (D, U1) -> (D) for D: DimName; + self: TranslationBase, right: TranslationBase, Output = OwnedTranslation; + TranslationBase::from_vector(self.vector - right.vector); ); + + +// TranslationBase × PointBase +// FIXME: we don't handle properly non-zero origins here. Do we want this to be the intended +// behavior? +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: &'a TranslationBase, right: &'b PointBase, Output = OwnedPoint; + right + &self.vector; 'a, 'b); + +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: &'a TranslationBase, right: PointBase, Output = OwnedPoint; + right + &self.vector; 'a); + +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: TranslationBase, right: &'b PointBase, Output = OwnedPoint; + right + self.vector; 'b); + +add_sub_impl!(Mul, mul, ClosedAdd; + (D, U1), (D, U1) -> (D) for D: DimName; + self: TranslationBase, right: PointBase, Output = OwnedPoint; + right + self.vector; ); + + +// TranslationBase *= TranslationBase +add_sub_assign_impl!(MulAssign, mul_assign, ClosedAdd; + (D, U1), (D, U1) for D: DimName; + self: TranslationBase, right: &'b TranslationBase; + self.vector += &right.vector; 'b); + +add_sub_assign_impl!(MulAssign, mul_assign, ClosedAdd; + (D, U1), (D, U1) for D: DimName; + self: TranslationBase, right: TranslationBase; + self.vector += right.vector; ); + + +add_sub_assign_impl!(DivAssign, div_assign, ClosedSub; + (D, U1), (D, U1) for D: DimName; + self: TranslationBase, right: &'b TranslationBase; + self.vector -= &right.vector; 'b); + +add_sub_assign_impl!(DivAssign, div_assign, ClosedSub; + (D, U1), (D, U1) for D: DimName; + self: TranslationBase, right: TranslationBase; + self.vector -= right.vector; ); + +/* +// TranslationBase × Matrix +add_sub_impl!(Mul, mul; + (D1, D1), (R2, C2) for D1, R2, C2; + self: &'a TranslationBase, right: &'b Matrix, Output = MatrixMul; + self.vector() * right; 'a, 'b); + +add_sub_impl!(Mul, mul; + (D1, D1), (R2, C2) for D1, R2, C2; + self: &'a TranslationBase, right: Matrix, Output = MatrixMul; + self.vector() * right; 'a); + +add_sub_impl!(Mul, mul; + (D1, D1), (R2, C2) for D1, R2, C2; + self: TranslationBase, right: &'b Matrix, Output = MatrixMul; + self.unwrap() * right; 'b); + +add_sub_impl!(Mul, mul; + (D1, D1), (R2, C2) for D1, R2, C2; + self: TranslationBase, right: Matrix, Output = MatrixMul; + self.unwrap() * right; ); + +// Matrix × TranslationBase +add_sub_impl!(Mul, mul; + (R1, C1), (D2, D2) for R1, C1, D2; + self: &'a Matrix, right: &'b TranslationBase, Output = MatrixMul; + self * right.vector(); 'a, 'b); + +add_sub_impl!(Mul, mul; + (R1, C1), (D2, D2) for R1, C1, D2; + self: &'a Matrix, right: TranslationBase, Output = MatrixMul; + self * right.unwrap(); 'a); + +add_sub_impl!(Mul, mul; + (R1, C1), (D2, D2) for R1, C1, D2; + self: Matrix, right: &'b TranslationBase, Output = MatrixMul; + self * right.vector(); 'b); + + +add_sub_impl!(Mul, mul; + (R1, C1), (D2, D2) for R1, C1, D2; + self: Matrix, right: TranslationBase, Output = MatrixMul; + self * right.unwrap(); ); + +// Matrix *= TranslationBase +md_assign_impl!(MulAssign, mul_assign; + (R1, C1), (C1, C1) for R1, C1; + self: Matrix, right: &'b TranslationBase; + self.mul_assign(right.vector()); 'b); + +md_assign_impl!(MulAssign, mul_assign; + (R1, C1), (C1, C1) for R1, C1; + self: Matrix, right: TranslationBase; + self.mul_assign(right.unwrap()); ); + + +md_assign_impl!(DivAssign, div_assign; + (R1, C1), (C1, C1) for R1, C1; + self: Matrix, right: &'b TranslationBase; + self.mul_assign(right.inverse().vector()); 'b); + +md_assign_impl!(DivAssign, div_assign; + (R1, C1), (C1, C1) for R1, C1; + self: Matrix, right: TranslationBase; + self.mul_assign(right.inverse().unwrap()); ); +*/ diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs new file mode 100644 index 00000000..29d07ab6 --- /dev/null +++ b/src/geometry/unit_complex.rs @@ -0,0 +1,115 @@ +use std::fmt; +use num_complex::Complex; + +use alga::general::Real; +use core::{Unit, SquareMatrix, Vector1}; +use core::dimension::{U2, U3}; +use core::allocator::{OwnedAllocator, Allocator}; +use core::storage::OwnedStorage; +use geometry::{RotationBase, OwnedRotation}; + +/// A complex number with a norm equal to 1. +pub type UnitComplex = Unit>; + +impl UnitComplex { + /// The rotation angle in `]-pi; pi]` of this unit complex number. + #[inline] + pub fn angle(&self) -> N { + self.complex().im.atan2(self.complex().re) + } + + /// The rotation angle returned as a 1-dimensional vector. + #[inline] + pub fn scaled_axis(&self) -> Vector1 { + Vector1::new(self.angle()) + } + + /// The underlying complex number. + /// + /// Same as `self.as_ref()`. + #[inline] + pub fn complex(&self) -> &Complex { + self.as_ref() + } + + /// Compute the conjugate of this unit complex number. + #[inline] + pub fn conjugate(&self) -> Self { + UnitComplex::new_unchecked(self.as_ref().conj()) + } + + /// Inverts this complex number if it is not zero. + #[inline] + pub fn inverse(&self) -> Self { + self.conjugate() + } + + /// The rotation angle needed to make `self` and `other` coincide. + #[inline] + pub fn angle_to(&self, other: &Self) -> N { + let delta = self.rotation_to(other); + delta.angle() + } + + /// The unit complex number needed to make `self` and `other` coincide. + /// + /// The result is such that: `self.rotation_to(other) * self == other`. + #[inline] + pub fn rotation_to(&self, other: &Self) -> Self { + other / self + } + + /// Compute in-place the conjugate of this unit complex number. + #[inline] + pub fn conjugate_mut(&mut self) { + let me = self.as_mut_unchecked(); + me.im = -me.im; + } + + /// Inverts in-place this unit complex number. + #[inline] + pub fn inverse_mut(&mut self) { + self.conjugate_mut() + } + + /// Raise this unit complex number to a given floating power. + /// + /// This returns the unit complex number that identifies a rotation angle equal to + /// `self.angle() × n`. + #[inline] + pub fn powf(&self, n: N) -> Self { + Self::from_angle(self.angle() * n) + } + + /// Builds the rotation matrix corresponding to this unit complex number. + #[inline] + pub fn to_rotation_matrix(&self) -> RotationBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + let r = self.complex().re; + let i = self.complex().im; + + RotationBase::from_matrix_unchecked( + SquareMatrix::<_, U2, _>::new( + r, -i, + i, r + ) + ) + } + + /// Converts this unit complex number into its equivalent homogeneous transformation matrix. + #[inline] + pub fn to_homogeneous(&self) -> SquareMatrix + where S: OwnedStorage, + S::Alloc: OwnedAllocator + + Allocator { + let r: OwnedRotation = self.to_rotation_matrix(); + r.to_homogeneous() + } +} + +impl fmt::Display for UnitComplex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "UnitComplex angle: {}", self.angle()) + } +} diff --git a/src/geometry/unit_complex_alga.rs b/src/geometry/unit_complex_alga.rs new file mode 100644 index 00000000..9c03b354 --- /dev/null +++ b/src/geometry/unit_complex_alga.rs @@ -0,0 +1,188 @@ +use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, + AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; +use alga::linear::{Transformation, AffineTransformation, Similarity, Isometry, DirectIsometry, + OrthogonalTransformation, Rotation, ProjectiveTransformation}; + +use core::ColumnVector; +use core::storage::OwnedStorage; +use core::allocator::OwnedAllocator; +use core::dimension::{U1, U2}; +use geometry::{PointBase, UnitComplex}; + +/* + * + * Implementations for UnitComplex. + * + */ +impl Identity for UnitComplex { + #[inline] + fn identity() -> Self { + Self::identity() + } +} + +impl AbstractMagma for UnitComplex { + #[inline] + fn operate(&self, rhs: &Self) -> Self { + self * rhs + } +} + +impl Inverse for UnitComplex { + #[inline] + fn inverse(&self) -> Self { + self.inverse() + } + + #[inline] + fn inverse_mut(&mut self) { + self.inverse_mut() + } +} + +macro_rules! impl_structures( + ($($marker: ident<$operator: ident>),* $(,)*) => {$( + impl $marker<$operator> for UnitComplex { + } + )*} +); + + +impl_structures!( + AbstractSemigroup, + AbstractQuasigroup, + AbstractMonoid, + AbstractLoop, + AbstractGroup +); + +impl Transformation> for UnitComplex + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn transform_point(&self, pt: &PointBase) -> PointBase { + self * pt + } + + #[inline] + fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self * v + } +} + +impl ProjectiveTransformation> for UnitComplex + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + // FIXME: would it be useful performancewise not to call inverse explicitly (i-e. implement + // the inverse transformation explicitly here) ? + self.inverse() * pt + } + + #[inline] + fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + self.inverse() * v + } +} + +impl AffineTransformation> for UnitComplex + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Rotation = Self; + type NonUniformScaling = Id; + type Translation = Id; + + #[inline] + fn decompose(&self) -> (Id, Self, Id, Self) { + (Id::new(), self.clone(), Id::new(), Self::identity()) + } + + #[inline] + fn append_translation(&self, _: &Self::Translation) -> Self { + self.clone() + } + + #[inline] + fn prepend_translation(&self, _: &Self::Translation) -> Self { + self.clone() + } + + #[inline] + fn append_rotation(&self, r: &Self::Rotation) -> Self { + r * self + } + + #[inline] + fn prepend_rotation(&self, r: &Self::Rotation) -> Self { + self * r + } + + #[inline] + fn append_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } + + #[inline] + fn prepend_scaling(&self, _: &Self::NonUniformScaling) -> Self { + self.clone() + } +} + +impl Similarity> for UnitComplex + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + type Scaling = Id; + + #[inline] + fn translation(&self) -> Id { + Id::new() + } + + #[inline] + fn rotation(&self) -> Self { + self.clone() + } + + #[inline] + fn scaling(&self) -> Id { + Id::new() + } +} + +macro_rules! marker_impl( + ($($Trait: ident),*) => {$( + impl $Trait> for UnitComplex + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { } + )*} +); + +marker_impl!(Isometry, DirectIsometry, OrthogonalTransformation); + + + +impl Rotation> for UnitComplex + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn powf(&self, n: N) -> Option { + Some(self.powf(n)) + } + + #[inline] + fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option { + Some(Self::rotation_between(a, b)) + } + + #[inline] + fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Option { + Some(Self::scaled_rotation_between(a, b, s)) + } +} diff --git a/src/geometry/unit_complex_construction.rs b/src/geometry/unit_complex_construction.rs new file mode 100644 index 00000000..8be17a63 --- /dev/null +++ b/src/geometry/unit_complex_construction.rs @@ -0,0 +1,107 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; + +use num::One; +use num_complex::Complex; +use rand::{Rand, Rng}; + +use alga::general::Real; +use core::ColumnVector; +use core::dimension::{U1, U2}; +use core::storage::Storage; +use geometry::{UnitComplex, RotationBase}; + + +impl UnitComplex { + /// The unit complex number multiplicative identity. + #[inline] + pub fn identity() -> Self { + Self::new_unchecked(Complex::new(N::one(), N::zero())) + } + + /// Builds the unit complex number corresponding to the rotation with the angle. + #[inline] + pub fn new(angle: N) -> Self { + let (s, c) = angle.sin_cos(); + UnitComplex::new_unchecked(Complex::new(c, s)) + } + + /// Builds the unit complex number corresponding to the rotation with the angle. + /// + /// Same as `Self::new(angle)`. + #[inline] + pub fn from_angle(angle: N) -> Self { + Self::new(angle) + } + + /// Builds a unit complex rotation from an angle in radian wrapped in a 1-dimensional vector. + /// + /// Equivalent to `Self::new(axisangle[0])`. + #[inline] + pub fn from_scaled_axis>(axisangle: ColumnVector) -> Self { + Self::from_angle(axisangle[0]) + } + + /// Creates a new unit complex number from a complex number. + /// + /// The input complex number will be normalized. + #[inline] + pub fn from_complex(q: Complex) -> Self { + Self::new_unchecked(q / (q.im * q.im + q.re * q.re).sqrt()) + } + + /// Builds the unit complex number from the corresponding 2D rotation matrix. + #[inline] + pub fn from_rotation_matrix>(rotmat: &RotationBase) -> Self { + Self::new_unchecked(Complex::new(rotmat[(0, 0)], rotmat[(1, 0)])) + } + + /// The unit complex needed to make `a` and `b` be collinear and point toward the same + /// direction. + #[inline] + pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Self + where SB: Storage, + SC: Storage { + Self::scaled_rotation_between(a, b, N::one()) + } + + /// The smallest rotation needed to make `a` and `b` collinear and point toward the same + /// direction, raised to the power `s`. + #[inline] + pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Self + where SB: Storage, + SC: Storage { + if let (Some(na), Some(nb)) = (a.try_normalize(N::zero()), b.try_normalize(N::zero())) { + let sang = na.perp(&nb); + let cang = na.dot(&nb); + + Self::from_angle(sang.atan2(cang) * s) + } + else { + Self::identity() + } + } +} + +impl One for UnitComplex { + #[inline] + fn one() -> Self { + Self::identity() + } +} + +impl Rand for UnitComplex { + #[inline] + fn rand(rng: &mut R) -> Self { + UnitComplex::from_angle(N::rand(rng)) + } +} + +#[cfg(feature="arbitrary")] +impl Arbitrary for UnitComplex { + #[inline] + fn arbitrary(g: &mut G) -> Self { + UnitComplex::from_angle(N::arbitrary(g)) + + } +} diff --git a/src/geometry/unit_complex_ops.rs b/src/geometry/unit_complex_ops.rs new file mode 100644 index 00000000..db09fe0f --- /dev/null +++ b/src/geometry/unit_complex_ops.rs @@ -0,0 +1,309 @@ +use std::ops::{Mul, MulAssign, Div, DivAssign}; + +use alga::general::Real; +use core::{Unit, ColumnVector, OwnedColumnVector}; +use core::dimension::{U1, U2}; +use core::storage::Storage; +use geometry::{UnitComplex, RotationBase, PointBase, OwnedPoint}; + +/* + * This file provides: + * =================== + * + * UnitComplex × UnitComplex + * UnitComplex × RotationBase -> UnitComplex + * RotationBase × UnitComplex -> UnitComplex + * + * UnitComplex ÷ UnitComplex + * UnitComplex ÷ RotationBase -> UnitComplex + * RotationBase ÷ UnitComplex -> UnitComplex + * + * + * UnitComplex × PointBase + * UnitComplex × ColumnVector + * UnitComplex × Unit + * + * NOTE: -UnitComplex is already provided by `Unit`. + * + * + * (Assignment Operators) + * + * UnitComplex ×= UnitComplex + * UnitComplex ×= RotationBase + * + * UnitComplex ÷= UnitComplex + * UnitComplex ÷= RotationBase + * + * FIXME: RotationBase ×= UnitComplex + * FIXME: RotationBase ÷= UnitComplex + * + */ + +// UnitComplex × UnitComplex +impl Mul> for UnitComplex { + type Output = UnitComplex; + + #[inline] + fn mul(self, rhs: UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.unwrap() * rhs.unwrap()) + } +} + +impl<'a, N: Real> Mul> for &'a UnitComplex { + type Output = UnitComplex; + + #[inline] + fn mul(self, rhs: UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.complex() * rhs.unwrap()) + } +} + +impl<'b, N: Real> Mul<&'b UnitComplex> for UnitComplex { + type Output = UnitComplex; + + #[inline] + fn mul(self, rhs: &'b UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.unwrap() * rhs.complex()) + } +} + +impl<'a, 'b, N: Real> Mul<&'b UnitComplex> for &'a UnitComplex { + type Output = UnitComplex; + + #[inline] + fn mul(self, rhs: &'b UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.complex() * rhs.complex()) + } +} + +// UnitComplex ÷ UnitComplex +impl Div> for UnitComplex { + type Output = UnitComplex; + + #[inline] + fn div(self, rhs: UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.unwrap() * rhs.conjugate().unwrap()) + } +} + +impl<'a, N: Real> Div> for &'a UnitComplex { + type Output = UnitComplex; + + #[inline] + fn div(self, rhs: UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.complex() * rhs.conjugate().unwrap()) + } +} + +impl<'b, N: Real> Div<&'b UnitComplex> for UnitComplex { + type Output = UnitComplex; + + #[inline] + fn div(self, rhs: &'b UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.unwrap() * rhs.conjugate().unwrap()) + } +} + +impl<'a, 'b, N: Real> Div<&'b UnitComplex> for &'a UnitComplex { + type Output = UnitComplex; + + #[inline] + fn div(self, rhs: &'b UnitComplex) -> UnitComplex { + Unit::new_unchecked(self.complex() * rhs.conjugate().unwrap()) + } +} + +macro_rules! complex_op_impl( + ($Op: ident, $op: ident; + ($RDim: ident, $CDim: ident); + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; + $action: expr; $($lives: tt),*) => { + impl<$($lives ,)* N, S> $Op<$Rhs> for $Lhs + where N: Real, + S: Storage { + type Output = $Result; + + #[inline] + fn $op($lhs, $rhs: $Rhs) -> Self::Output { + $action + } + } + } +); + +macro_rules! complex_op_impl_all( + ($Op: ident, $op: ident; + ($RDim: ident, $CDim: ident); + $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; + [val val] => $action_val_val: expr; + [ref val] => $action_ref_val: expr; + [val ref] => $action_val_ref: expr; + [ref ref] => $action_ref_ref: expr;) => { + + complex_op_impl!($Op, $op; + ($RDim, $CDim); + $lhs: $Lhs, $rhs: $Rhs, Output = $Result; + $action_val_val; ); + + complex_op_impl!($Op, $op; + ($RDim, $CDim); + $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Result; + $action_ref_val; 'a); + + complex_op_impl!($Op, $op; + ($RDim, $CDim); + $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Result; + $action_val_ref; 'b); + + complex_op_impl!($Op, $op; + ($RDim, $CDim); + $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Result; + $action_ref_ref; 'a, 'b); + + + } +); + + +// UnitComplex × RotationBase +complex_op_impl_all!( + Mul, mul; + (U2, U2); + self: UnitComplex, rhs: RotationBase, Output = UnitComplex; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => self * UnitComplex::from_rotation_matrix(rhs); +); + +// UnitComplex ÷ RotationBase +complex_op_impl_all!( + Div, div; + (U2, U2); + self: UnitComplex, rhs: RotationBase, Output = UnitComplex; + [val val] => &self / &rhs; + [ref val] => self / &rhs; + [val ref] => &self / rhs; + [ref ref] => self * UnitComplex::from_rotation_matrix(rhs).inverse(); +); + + +// RotationBase × UnitComplex +complex_op_impl_all!( + Mul, mul; + (U2, U2); + self: RotationBase, rhs: UnitComplex, Output = UnitComplex; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => UnitComplex::from_rotation_matrix(self) * rhs; +); + +// RotationBase ÷ UnitComplex +complex_op_impl_all!( + Div, div; + (U2, U2); + self: RotationBase, rhs: UnitComplex, Output = UnitComplex; + [val val] => &self / &rhs; + [ref val] => self / &rhs; + [val ref] => &self / rhs; + [ref ref] => UnitComplex::from_rotation_matrix(self) * rhs.inverse(); +); + +// UnitComplex × PointBase +complex_op_impl_all!( + Mul, mul; + (U2, U1); + self: UnitComplex, rhs: PointBase, Output = OwnedPoint; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => PointBase::from_coordinates(self * &rhs.coords); +); + +// UnitComplex × ColumnVector +complex_op_impl_all!( + Mul, mul; + (U2, U1); + self: UnitComplex, rhs: ColumnVector, Output = OwnedColumnVector; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let i = self.as_ref().im; + let r = self.as_ref().re; + OwnedColumnVector::<_, U2, S::Alloc>::new(r * rhs[0] - i * rhs[0], i * rhs[1] + r * rhs[1]) + }; +); + +// UnitComplex × Unit +complex_op_impl_all!( + Mul, mul; + (U2, U1); + self: UnitComplex, rhs: Unit>, Output = Unit>; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => Unit::new_unchecked(self * rhs.as_ref()); +); + +// UnitComplex ×= UnitComplex +impl MulAssign> for UnitComplex { + #[inline] + fn mul_assign(&mut self, rhs: UnitComplex) { + *self = &*self * rhs + } +} + +impl<'b, N: Real> MulAssign<&'b UnitComplex> for UnitComplex { + #[inline] + fn mul_assign(&mut self, rhs: &'b UnitComplex) { + *self = &*self * rhs + } +} + +// UnitComplex /= UnitComplex +impl DivAssign> for UnitComplex { + #[inline] + fn div_assign(&mut self, rhs: UnitComplex) { + *self = &*self / rhs + } +} + +impl<'b, N: Real> DivAssign<&'b UnitComplex> for UnitComplex { + #[inline] + fn div_assign(&mut self, rhs: &'b UnitComplex) { + *self = &*self / rhs + } +} + + +// UnitComplex ×= RotationBase +impl> MulAssign> for UnitComplex { + #[inline] + fn mul_assign(&mut self, rhs: RotationBase) { + *self = &*self * rhs + } +} + +impl<'b, N: Real, S: Storage> MulAssign<&'b RotationBase> for UnitComplex { + #[inline] + fn mul_assign(&mut self, rhs: &'b RotationBase) { + *self = &*self * rhs + } +} + +// UnitComplex ÷= RotationBase +impl> DivAssign> for UnitComplex { + #[inline] + fn div_assign(&mut self, rhs: RotationBase) { + *self = &*self / rhs + } +} + +impl<'b, N: Real, S: Storage> DivAssign<&'b RotationBase> for UnitComplex { + #[inline] + fn div_assign(&mut self, rhs: &'b RotationBase) { + *self = &*self / rhs + } +} diff --git a/src/lib.rs b/src/lib.rs index 21d7fce5..9d269e25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,183 +1,131 @@ -/*! -# nalgebra - -**nalgebra** is a low-dimensional linear algebra library written for Rust targeting: - -* General-purpose linear algebra (still lacks a lot of features…) -* Real time computer graphics. -* Real time computer physics. - -## Using **nalgebra** -You will need the last stable build of the [rust compiler](http://www.rust-lang.org) -and the official package manager: [cargo](https://github.com/rust-lang/cargo). - -Simply add the following to your `Cargo.toml` file: - -```.ignore -[dependencies] -nalgebra = "0.10.*" -``` - - -All the functionality of **nalgebra** is grouped in one place: the root module `nalgebra::`. This -module re-exports everything and includes free functions for all traits methods performing -out-of-place operations. - -Thus, you can import the whole prelude using: - -```.ignore -use nalgebra::*; -``` - -However, the recommended way to use **nalgebra** is to import types and traits -explicitly, and call free-functions using the `na::` prefix: - -```.rust -extern crate nalgebra as na; -use na::{Vector3, Rotation3, Rotation}; - -fn main() { - let a = Vector3::new(1.0f64, 1.0, 1.0); - let mut b = Rotation3::new(na::zero()); - - b.append_rotation_mut(&a); - - assert!(na::approx_eq(&na::rotation(&b), &a)); -} -``` - - -## Features -**nalgebra** is meant to be a general-purpose, low-dimensional, linear algebra library, with -an optimized set of tools for computer graphics and physics. Those features include: - -* Vectors with predefined static sizes: `Vector1`, `Vector2`, `Vector3`, `Vector4`, `Vector5`, `Vector6`. -* Vector with a user-defined static size: `VectorN` (available only with the `generic_sizes` feature). -* Points with static sizes: `Point1`, `Point2`, `Point3`, `Point4`, `Point5`, `Point6`. -* Square matrices with static sizes: `Matrix1`, `Matrix2`, `Matrix3`, `Matrix4`, `Matrix5`, `Matrix6 `. -* Rotation matrices: `Rotation2`, `Rotation3` -* Quaternions: `Quaternion`, `Unit`. -* Unit-sized values (unit vectors, unit quaternions, etc.): `Unit`, e.g., `Unit>`. -* Isometries (translation ⨯ rotation): `Isometry2`, `Isometry3` -* Similarity transformations (translation ⨯ rotation ⨯ uniform scale): `Similarity2`, `Similarity3`. -* 3D projections for computer graphics: `Persp3`, `PerspMatrix3`, `Ortho3`, `OrthoMatrix3`. -* Dynamically sized heap-allocated vector: `DVector`. -* Dynamically sized stack-allocated vectors with a maximum size: `DVector1` to `DVector6`. -* Dynamically sized heap-allocated (square or rectangular) matrix: `DMatrix`. -* Linear algebra and data analysis operators: `Covariance`, `Mean`, `qr`, `cholesky`. -* Almost one trait per functionality: useful for generic programming. -*/ - -#![deny(non_camel_case_types)] -#![deny(unused_parens)] -#![deny(non_upper_case_globals)] -#![deny(unused_qualifications)] -#![deny(unused_results)] -#![warn(missing_docs)] -#![doc(html_root_url = "http://nalgebra.org/doc")] +// #![feature(plugin)] +// +// #![plugin(clippy)] +// #![allow(too_many_arguments)] +// #![allow(derive_hash_xor_eq)] +// #![allow(len_without_is_empty)] +// #![allow(transmute_ptr_to_ref)] +#[cfg(feature = "arbitrary")] +extern crate quickcheck; extern crate rustc_serialize; +extern crate num_traits as num; +extern crate num_complex; extern crate rand; -extern crate num; - -#[cfg(feature="generic_sizes")] +#[macro_use] +extern crate approx; +extern crate typenum; extern crate generic_array; -#[cfg(feature="arbitrary")] -extern crate quickcheck; +extern crate alga; -#[cfg(feature="abstract_algebra")] -extern crate algebra; -use std::cmp; -use std::ops::{Neg, Mul}; -use num::{Zero, One}; -pub use traits::{ - Absolute, - AbsoluteRotate, - ApproxEq, - Axpy, - Basis, - BaseFloat, - BaseNum, - Bounded, - Cast, - Column, - ColumnSlice, RowSlice, - Covariance, - Cross, - CrossMatrix, - Determinant, - Diagonal, - Dimension, - Dot, - EigenQR, - Eye, - FloatPoint, - FloatVector, - FromHomogeneous, - Indexable, - Inverse, - Iterable, - IterableMut, - Matrix, - Mean, - Norm, - NumPoint, - NumVector, - Origin, - Outer, - PartialOrder, - PartialOrdering, - PointAsVector, - Repeat, - Rotate, Rotation, RotationMatrix, RotationWithTranslation, RotationTo, - Row, - Shape, - SquareMatrix, - ToHomogeneous, - Transform, Transformation, - Translate, Translation, - Transpose, - UniformSphereSample -}; - -#[cfg(feature="generic_sizes")] -pub use structs::VectorN; - -pub use structs::{ - Identity, - DMatrix, DMatrix1, DMatrix2, DMatrix3, DMatrix4, DMatrix5, DMatrix6, - DVector, DVector1, DVector2, DVector3, DVector4, DVector5, DVector6, - Isometry2, Isometry3, - Similarity2, Similarity3, - Matrix1, Matrix2, Matrix3, Matrix4, - Matrix5, Matrix6, - Rotation2, Rotation3, - Vector1, Vector2, Vector3, Vector4, Vector5, Vector6, - Point1, Point2, Point3, Point4, Point5, Point6, - Perspective3, PerspectiveMatrix3, - Orthographic3, OrthographicMatrix3, - Quaternion, UnitQuaternion, - Unit -}; - -pub use linalg::{ - qr, - householder_matrix, - cholesky, - hessenberg -}; - -mod structs; +pub mod core; +pub mod geometry; mod traits; -mod linalg; -mod macros; -// mod lower_triangular; -// mod chol; +pub use core::*; +pub use geometry::*; +pub use traits::*; -/// Change the input value to ensure it is on the range `[min, max]`. + +use std::cmp::{self, PartialOrd, Ordering}; + +use num::Signed; +use alga::general::{Id, Identity, SupersetOf, MeetSemilattice, JoinSemilattice, Lattice, Inverse, + Multiplicative, Additive, AdditiveGroup}; +use alga::linear::SquareMatrix as AlgaSquareMatrix; +use alga::linear::{InnerSpace, NormedSpace, FiniteDimVectorSpace, EuclideanSpace}; + +/* + * + * Multiplicative identity. + * + */ +/// Gets the ubiquitous multiplicative identity element. +/// +/// Same as `Id::new()`. +#[inline] +pub fn id() -> Id { + Id::new() +} + +/// Gets the multiplicative identity element. +#[inline] +pub fn one>() -> T { + T::identity() +} + +/// Gets the additive identity element. +#[inline] +pub fn zero>() -> T { + T::identity() +} + +/// Gets the origin of the given point. +#[inline] +pub fn origin() -> P { + P::origin() +} + +/* + * + * Dimension + * + */ +/// The dimension of the given algebraic entity seen as a vector space. +#[inline] +pub fn dimension() -> usize { + V::dimension() +} + +/* + * + * Ordering + * + */ +// XXX: this is very naive and could probably be optimized for specific types. +// XXX: also, we might just want to use divisions, but assuming `val` is usually not far from `min` +// or `max`, would it still be more efficient? +/// Wraps `val` into the range `[min, max]` using modular arithmetics. +/// +/// The range must not be empty. +#[inline] +pub fn wrap(mut val: T, min: T, max: T) -> T + where T: Copy + PartialOrd + AdditiveGroup { + + assert!(min < max, "Invalid wrapping bounds."); + let width = max - min; + + if val < min { + val += width; + + while val < min { + val += width + } + + val + } + else if val > max { + val -= width; + + while val > max { + val -= width + } + + val + } + else { + val + } +} + +/// Returns a reference to the input value clamped to the interval `[min, max]`. +/// +/// In particular: +/// * If `min < val < max`, this returns `val`. +/// * If `val <= min`, this retuns `min`. +/// * If `val >= max`, this retuns `max`. #[inline] pub fn clamp(val: T, min: T, max: T) -> T { if val > min { @@ -205,754 +153,246 @@ pub fn min(a: T, b: T) -> T { cmp::min(a, b) } +/// The absolute value of `a`. +#[inline] +pub fn abs(a: &T) -> T { + a.abs() +} + /// Returns the infimum of `a` and `b`. #[inline] -pub fn inf(a: &T, b: &T) -> T { - PartialOrder::inf(a, b) +pub fn inf(a: &T, b: &T) -> T { + a.meet(b) } /// Returns the supremum of `a` and `b`. #[inline] -pub fn sup(a: &T, b: &T) -> T { - PartialOrder::sup(a, b) +pub fn sup(a: &T, b: &T) -> T { + a.join(b) +} + +/// Returns simultaneously the infimum and supremum of `a` and `b`. +#[inline] +pub fn inf_sup(a: &T, b: &T) -> (T, T) { + a.meet_join(b) } /// Compare `a` and `b` using a partial ordering relation. #[inline] -pub fn partial_cmp(a: &T, b: &T) -> PartialOrdering { - PartialOrder::partial_cmp(a, b) +pub fn partial_cmp(a: &T, b: &T) -> Option { + a.partial_cmp(b) } /// Returns `true` iff `a` and `b` are comparable and `a < b`. #[inline] -pub fn partial_lt(a: &T, b: &T) -> bool { - PartialOrder::partial_lt(a, b) +pub fn partial_lt(a: &T, b: &T) -> bool { + a.lt(b) } /// Returns `true` iff `a` and `b` are comparable and `a <= b`. #[inline] -pub fn partial_le(a: &T, b: &T) -> bool { - PartialOrder::partial_le(a, b) +pub fn partial_le(a: &T, b: &T) -> bool { + a.le(b) } /// Returns `true` iff `a` and `b` are comparable and `a > b`. #[inline] -pub fn partial_gt(a: &T, b: &T) -> bool { - PartialOrder::partial_gt(a, b) +pub fn partial_gt(a: &T, b: &T) -> bool { + a.gt(b) } /// Returns `true` iff `a` and `b` are comparable and `a >= b`. #[inline] -pub fn partial_ge(a: &T, b: &T) -> bool { - PartialOrder::partial_ge(a, b) +pub fn partial_ge(a: &T, b: &T) -> bool { + a.ge(b) } /// Return the minimum of `a` and `b` if they are comparable. #[inline] -pub fn partial_min<'a, T: PartialOrder>(a: &'a T, b: &'a T) -> Option<&'a T> { - PartialOrder::partial_min(a, b) +pub fn partial_min<'a, T: PartialOrd>(a: &'a T, b: &'a T) -> Option<&'a T> { + if let Some(ord) = a.partial_cmp(b) { + match ord { + Ordering::Greater => Some(b), + _ => Some(a), + } + } + else { + None + } } /// Return the maximum of `a` and `b` if they are comparable. #[inline] -pub fn partial_max<'a, T: PartialOrder>(a: &'a T, b: &'a T) -> Option<&'a T> { - PartialOrder::partial_max(a, b) +pub fn partial_max<'a, T: PartialOrd>(a: &'a T, b: &'a T) -> Option<&'a T> { + if let Some(ord) = a.partial_cmp(b) { + match ord { + Ordering::Less => Some(b), + _ => Some(a), + } + } + else { + None + } } /// Clamp `value` between `min` and `max`. Returns `None` if `value` is not comparable to /// `min` or `max`. #[inline] -pub fn partial_clamp<'a, T: PartialOrder>(value: &'a T, min: &'a T, max: &'a T) -> Option<&'a T> { - PartialOrder::partial_clamp(value, min, max) +pub fn partial_clamp<'a, T: PartialOrd>(value: &'a T, min: &'a T, max: &'a T) -> Option<&'a T> { + if let (Some(cmp_min), Some(cmp_max)) = (value.partial_cmp(min), value.partial_cmp(max)) { + if cmp_min == Ordering::Less { + Some(min) + } + else if cmp_max == Ordering::Greater { + Some(max) + } + else { + Some(value) + } + } + else { + None + } } -// -// -// Constructors -// -// - -/// Create a special identity object. -/// -/// Same as `Identity::new()`. +/// Sorts two values in increasing order using a partial ordering. #[inline] -pub fn identity() -> Identity { - Identity::new() -} - -/// Create a zero-valued value. -/// -/// This is the same as `std::num::zero()`. -#[inline] -pub fn zero() -> T { - Zero::zero() -} - -/// Tests is a value is iqual to zero. -#[inline] -pub fn is_zero(val: &T) -> bool { - val.is_zero() -} - -/// Create a one-valued value. -/// -/// This is the same as `std::num::one()`. -#[inline] -pub fn one() -> T { - One::one() -} - -// -// -// Geometry -// -// - -/// Returns the trivial origin of an affine space. -#[inline] -pub fn origin() -> P { - Origin::origin() -} - -/// Returns the center of two points. -#[inline] -pub fn center>(a: &P, b: &P) -> P - where

::Vector: Norm { - (*a + b.to_vector()) / ::cast(2.0) -} - -/* - * FloatPoint - */ -/// Returns the distance between two points. -#[inline] -pub fn distance>(a: &P, b: &P) -> N where

::Vector: Norm { - a.distance(b) -} - -/// Returns the squared distance between two points. -#[inline] -pub fn distance_squared>(a: &P, b: &P) -> N - where

::Vector: Norm { - a.distance_squared(b) -} - -/* - * Translation - */ - -/// Gets the translation applicable by `m`. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Vector3, Isometry3}; -/// -/// fn main() { -/// let t = Isometry3::new(Vector3::new(1.0f64, 1.0, 1.0), na::zero()); -/// let trans = na::translation(&t); -/// -/// assert!(trans == Vector3::new(1.0, 1.0, 1.0)); -/// } -/// ``` -#[inline] -pub fn translation>(m: &M) -> V { - m.translation() -} - -/// Gets the inverse translation applicable by `m`. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Vector3, Isometry3}; -/// -/// fn main() { -/// let t = Isometry3::new(Vector3::new(1.0f64, 1.0, 1.0), na::zero()); -/// let itrans = na::inverse_translation(&t); -/// -/// assert!(itrans == Vector3::new(-1.0, -1.0, -1.0)); -/// } -/// ``` -#[inline] -pub fn inverse_translation>(m: &M) -> V { - m.inverse_translation() -} - -/// Applies the translation `v` to a copy of `m`. -#[inline] -pub fn append_translation>(m: &M, v: &V) -> M { - Translation::append_translation(m, v) -} - -/* - * Translate

+ crates.io +

crates.io @@ -8,75 +11,6 @@

- Documentation | Forum + Users guide | Documentation | Forum

- -nalgebra -======== - -**nalgebra** is a low-dimensional linear algebra library written for Rust targeting: - -* General-purpose linear algebra (still lacks a lot of features…) -* Real time computer graphics. -* Real time computer physics. - -## Using **nalgebra** -You will need the last stable build of the [rust compiler](http://www.rust-lang.org) -and the official package manager: [cargo](https://github.com/rust-lang/cargo). - -Simply add the following to your `Cargo.toml` file: - -```.ignore -[dependencies] -nalgebra = "0.10.*" -``` - - -All the functionality of **nalgebra** is grouped in one place: the root module `nalgebra::`. This -module re-exports everything and includes free functions for all traits methods performing -out-of-place operations. - -Thus, you can import the whole prelude using: - -```.ignore -use nalgebra::*; -``` - -However, the recommended way to use **nalgebra** is to import types and traits -explicitly, and call free-functions using the `na::` prefix: - -```.rust -extern crate nalgebra as na; -use na::{Vector3, Rotation3, Rotation}; - -fn main() { - let a = Vector3::new(1.0f64, 1.0, 1.0); - let mut b = Rotation3::new(na::zero()); - - b.append_rotation_mut(&a); - - assert!(na::approx_eq(&na::rotation(&b), &a)); -} -``` - - -## Features -**nalgebra** is meant to be a general-purpose, low-dimensional, linear algebra library, with -an optimized set of tools for computer graphics and physics. Those features include: - -* Vectors with predefined static sizes: `Vector1`, `Vector2`, `Vector3`, `Vector4`, `Vector5`, `Vector6`. -* Vector with a user-defined static size: `VectorN` (available only with the `generic_sizes` feature). -* Points with static sizes: `Point1`, `Point2`, `Point3`, `Point4`, `Point5`, `Point6`. -* Square matrices with static sizes: `Matrix1`, `Matrix2`, `Matrix3`, `Matrix4`, `Matrix5`, `Matrix6 `. -* Rotation matrices: `Rotation2`, `Rotation3` -* Quaternions: `Quaternion`, `Unit`. -* Unit-sized values (unit vectors, unit quaternions, etc.): `Unit`, e.g., `Unit>`. -* Isometries (translation ⨯ rotation): `Isometry2`, `Isometry3` -* Similarity transformations (translation ⨯ rotation ⨯ uniform scale): `Similarity2`, `Similarity3`. -* 3D projections for computer graphics: `Persp3`, `PerspMatrix3`, `Ortho3`, `OrthoMatrix3`. -* Dynamically sized heap-allocated vector: `DVector`. -* Dynamically sized stack-allocated vectors with a maximum size: `DVector1` to `DVector6`. -* Dynamically sized heap-allocated (square or rectangular) matrix: `DMatrix`. -* Linear algebra and data analysis operators: `Covariance`, `Mean`, `qr`, `cholesky`. -* Almost one trait per functionality: useful for generic programming. diff --git a/examples/dimensional_genericity.rs b/examples/dimensional_genericity.rs new file mode 100644 index 00000000..df22b945 --- /dev/null +++ b/examples/dimensional_genericity.rs @@ -0,0 +1,66 @@ +extern crate alga; +extern crate nalgebra as na; + +use alga::general::Real; +use alga::linear::FiniteDimInnerSpace; +use na::{Unit, ColumnVector, OwnedColumnVector, Vector2, Vector3}; +use na::storage::Storage; +use na::dimension::{DimName, U1}; + +/// Reflects a vector wrt. the hyperplane with normal `plane_normal`. +fn reflect_wrt_hyperplane_with_algebraic_genericity(plane_normal: &Unit, vector: &V) -> V + where V: FiniteDimInnerSpace + Copy { + let n = plane_normal.as_ref(); // Get the underlying vector of type `V`. + *vector - *n * (n.dot(vector) * na::convert(2.0)) +} + + +/// Reflects a vector wrt. the hyperplane with normal `plane_normal`. +fn reflect_wrt_hyperplane_with_structural_genericity(plane_normal: &Unit>, + vector: &ColumnVector) + -> OwnedColumnVector + where N: Real, + D: DimName, + S: Storage { + let n = plane_normal.as_ref(); // Get the underlying V. + vector - n * (n.dot(vector) * na::convert(2.0)) +} + +/// Reflects a 2D vector wrt. the 2D line with normal `plane_normal`. +fn reflect_wrt_hyperplane2(plane_normal: &Unit>, + vector: &Vector2) + -> Vector2 + where N: Real { + let n = plane_normal.as_ref(); // Get the underlying Vector2 + vector - n * (n.dot(vector) * na::convert(2.0)) +} + +/// Reflects a 3D vector wrt. the 3D plane with normal `plane_normal`. +/// /!\ This is an exact replicate of `reflect_wrt_hyperplane2, but for 3D. +fn reflect_wrt_hyperplane3(plane_normal: &Unit>, + vector: &Vector3) + -> Vector3 + where N: Real { + let n = plane_normal.as_ref(); // Get the underlying Vector3 + vector - n * (n.dot(vector) * na::convert(2.0)) +} + + +fn main() { + let plane2 = Vector2::y_axis(); // 2D plane normal. + let plane3 = Vector3::y_axis(); // 3D plane normal. + + let v2 = Vector2::new(1.0, 2.0); // 2D vector to be reflected. + let v3 = Vector3::new(1.0, 2.0, 3.0); // 3D vector to be reflected. + + // We can call the same function for 2D and 3D. + assert_eq!(reflect_wrt_hyperplane_with_algebraic_genericity(&plane2, &v2).y, -2.0); + assert_eq!(reflect_wrt_hyperplane_with_algebraic_genericity(&plane3, &v3).y, -2.0); + + assert_eq!(reflect_wrt_hyperplane_with_structural_genericity(&plane2, &v2).y, -2.0); + assert_eq!(reflect_wrt_hyperplane_with_structural_genericity(&plane3, &v3).y, -2.0); + + // Call each specific implementation depending on the dimension. + assert_eq!(reflect_wrt_hyperplane2(&plane2, &v2).y, -2.0); + assert_eq!(reflect_wrt_hyperplane3(&plane3, &v3).y, -2.0); +} diff --git a/examples/homogeneous_coordinates.rs b/examples/homogeneous_coordinates.rs new file mode 100644 index 00000000..a18b5a72 --- /dev/null +++ b/examples/homogeneous_coordinates.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate approx; +extern crate nalgebra as na; + +use std::f32; +use na::{Vector2, Point2, Isometry2}; + + +fn use_dedicated_types() { + let iso = Isometry2::new(Vector2::new(1.0, 1.0), f32::consts::PI); + let pt = Point2::new(1.0, 0.0); + let vec = Vector2::x(); + + let transformed_pt = iso * pt; + let transformed_vec = iso * vec; + + assert_relative_eq!(transformed_pt, Point2::new(0.0, 1.0)); + assert_relative_eq!(transformed_vec, Vector2::new(-1.0, 0.0)); +} + +fn use_homogeneous_coordinates() { + let iso = Isometry2::new(Vector2::new(1.0, 1.0), f32::consts::PI); + let pt = Point2::new(1.0, 0.0); + let vec = Vector2::x(); + + // Compute using homogeneous coordinates. + let hom_iso = iso.to_homogeneous(); + let hom_pt = pt.to_homogeneous(); + let hom_vec = vec.to_homogeneous(); + + let hom_transformed_pt = hom_iso * hom_pt; + let hom_transformed_vec = hom_iso * hom_vec; + + // Convert back to the cartesian coordinates. + let transformed_pt = Point2::from_homogeneous(hom_transformed_pt).unwrap(); + let transformed_vec = Vector2::from_homogeneous(hom_transformed_vec).unwrap(); + + assert_relative_eq!(transformed_pt, Point2::new(0.0, 1.0)); + assert_relative_eq!(transformed_vec, Vector2::new(-1.0, 0.0)); +} + +fn main() { + use_dedicated_types(); + use_homogeneous_coordinates(); +} diff --git a/examples/identity.rs b/examples/identity.rs new file mode 100644 index 00000000..1805bf1e --- /dev/null +++ b/examples/identity.rs @@ -0,0 +1,42 @@ +extern crate alga; +extern crate nalgebra as na; + + +use alga::linear::Transformation; +use na::{Id, Vector3, Point3, Isometry3}; + +/* + * Applies `n` times the transformation `t` to the vector `v` and sum each + * intermediate value. + */ +fn complicated_algorithm(v: &Vector3, t: &T, n: usize) -> Vector3 + where T: Transformation> { + + let mut result = *v; + + // Do lots of operations involving t. + for _ in 0 .. n { + result = v + t.transform_vector(&result); + } + + result +} + + +/* + * The two following calls are equivalent in term of result. + */ +fn main() { + let v = Vector3::new(1.0, 2.0, 3.0); + + // The specialization generated by the compiler will do vector additions only. + let result1 = complicated_algorithm(&v, &Id::new(), 100000); + + // The specialization generated by the compiler will also include matrix multiplications. + let iso = Isometry3::identity(); + let result2 = complicated_algorithm(&v, &iso, 100000); + + // They both return the same result. + assert!(result1 == Vector3::new(100001.0, 200002.0, 300003.0)); + assert!(result2 == Vector3::new(100001.0, 200002.0, 300003.0)); +} diff --git a/examples/matrix_construction.rs b/examples/matrix_construction.rs new file mode 100644 index 00000000..4eb4ad06 --- /dev/null +++ b/examples/matrix_construction.rs @@ -0,0 +1,62 @@ +extern crate nalgebra as na; + +use na::{Vector2, RowVector3, Matrix2x3, DMatrix}; + + +fn main() { + // All the following matrices are equal but constructed in different ways. + let m = Matrix2x3::new(1.1, 1.2, 1.3, + 2.1, 2.2, 2.3); + + let m1 = Matrix2x3::from_rows(&[ + RowVector3::new(1.1, 1.2, 1.3), + RowVector3::new(2.1, 2.2, 2.3) + ]); + + let m2 = Matrix2x3::from_columns(&[ + Vector2::new(1.1, 2.1), + Vector2::new(1.2, 2.2), + Vector2::new(1.3, 2.3) + ]); + + let m3 = Matrix2x3::from_row_slice(&[ + 1.1, 1.2, 1.3, + 2.1, 2.2, 2.3 + ]); + + let m4 = Matrix2x3::from_column_slice(&[ + 1.1, 2.1, + 1.2, 2.2, + 1.3, 2.3 + ]); + + let m5 = Matrix2x3::from_fn(|r, c| (r + 1) as f32 + (c + 1) as f32 / 10.0); + + let m6 = Matrix2x3::from_iterator([ 1.1f32, 2.1, 1.2, 2.2, 1.3, 2.3 ].iter().cloned()); + + assert_eq!(m, m1); assert_eq!(m, m2); assert_eq!(m, m3); + assert_eq!(m, m4); assert_eq!(m, m5); assert_eq!(m, m6); + + // All the following matrices are equal but constructed in different ways. + // This time, we used a dynamically-sized matrix to show the extra arguments + // for the matrix shape. + let dm = DMatrix::from_row_slice(4, 3, &[ + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0 + ]); + + let dm1 = DMatrix::from_diagonal_element(4, 3, 1.0); + let dm2 = DMatrix::identity(4, 3); + let dm3 = DMatrix::from_fn(4, 3, |r, c| if r == c { 1.0 } else { 0.0 }); + let dm4 = DMatrix::from_iterator(4, 3, [ + // Components listed column-by-column. + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0 + ].iter().cloned()); + + assert_eq!(dm, dm1); assert_eq!(dm, dm2); + assert_eq!(dm, dm3); assert_eq!(dm, dm4); +} diff --git a/examples/mvp.rs b/examples/mvp.rs new file mode 100644 index 00000000..7a38c671 --- /dev/null +++ b/examples/mvp.rs @@ -0,0 +1,28 @@ +#![allow(unused_variables)] + +extern crate nalgebra as na; + +use na::{Vector3, Point3, Isometry3, Perspective3}; + +fn main() { + // Our object is translated along the x axis. + let model = Isometry3::new(Vector3::x(), na::zero()); + + // Our camera looks toward the point (1.0, 0.0, 0.0). + // It is located at (0.0, 0.0, 1.0). + let eye = Point3::new(0.0, 0.0, 1.0); + let target = Point3::new(1.0, 0.0, 0.0); + let view = Isometry3::look_at_rh(&eye, &target, &Vector3::y()); + + // A perspective projection. + let projection = Perspective3::new(16.0 / 9.0, 3.14 / 2.0, 1.0, 1000.0); + + // The combination of the model with the view is still an isometry. + let model_view = model * view; + + // Convert everything to a `Matrix4` so that they can be combined. + let mat_model_view = model_view.to_homogeneous(); + + // Combine everything. + let model_view_projection = projection.as_matrix() * mat_model_view; +} diff --git a/examples/raw_pointer.rs b/examples/raw_pointer.rs new file mode 100644 index 00000000..ec960da4 --- /dev/null +++ b/examples/raw_pointer.rs @@ -0,0 +1,35 @@ +extern crate nalgebra as na; + +use na::{Vector3, Point3, Matrix3}; + +fn main() { + let v = Vector3::new(1.0f32, 0.0, 1.0); + let p = Point3::new(1.0f32, 0.0, 1.0); + let m = na::one::>(); + + // Convert to arrays. + let v_array = v.as_slice(); + let p_array = p.coords.as_slice(); + let m_array = m.as_slice(); + + // Get data pointers. + let v_pointer = v_array.as_ptr(); + let p_pointer = p_array.as_ptr(); + let m_pointer = m_array.as_ptr(); + + /* Then pass the raw pointers to some graphics API. */ + + unsafe { + assert_eq!(*v_pointer, 1.0); + assert_eq!(*v_pointer.offset(1), 0.0); + assert_eq!(*v_pointer.offset(2), 1.0); + + assert_eq!(*p_pointer, 1.0); + assert_eq!(*p_pointer.offset(1), 0.0); + assert_eq!(*p_pointer.offset(2), 1.0); + + assert_eq!(*m_pointer, 1.0); + assert_eq!(*m_pointer.offset(4), 1.0); + assert_eq!(*m_pointer.offset(8), 1.0); + } +} diff --git a/examples/scalar_genericity.rs b/examples/scalar_genericity.rs new file mode 100644 index 00000000..156d93ab --- /dev/null +++ b/examples/scalar_genericity.rs @@ -0,0 +1,34 @@ +extern crate alga; +extern crate nalgebra as na; + +use alga::general::{RingCommutative, Real}; +use na::{Vector3, Scalar}; + +fn print_vector(m: &Vector3) { + println!("{:?}", m) +} + +fn print_squared_norm(v: &Vector3) { + // NOTE: alternatively, nalgebra already defines `v.squared_norm()`. + let sqnorm = v.dot(v); + + println!("{:?}", sqnorm); +} + +fn print_norm(v: &Vector3) { + // NOTE: alternatively, nalgebra already defines `v.norm()`. + let norm = v.dot(v).sqrt(); + + // The Real bound implies that N is Display so we can + // use "{}" instead of "{:?}" for the format string. + println!("{}", norm) +} + +fn main() { + let v1 = Vector3::new(1, 2, 3); + let v2 = Vector3::new(1.0, 2.0, 3.0); + + print_vector(&v1); + print_squared_norm(&v1); + print_norm(&v2); +} diff --git a/examples/screen_to_view_coords.rs b/examples/screen_to_view_coords.rs new file mode 100644 index 00000000..4d6bc351 --- /dev/null +++ b/examples/screen_to_view_coords.rs @@ -0,0 +1,24 @@ +#![allow(unused_variables)] + +extern crate nalgebra as na; + +use na::{Point2, Point3, Perspective3, Unit}; + + +fn main() { + let projection = Perspective3::new(800.0 / 600.0, 3.14 / 2.0, 1.0, 1000.0); + let screen_point = Point2::new(10.0f32, 20.0); + + // Compute two points in clip-space. + // "ndc" = normalized device coordinates. + let near_ndc_point = Point3::new(screen_point.x / 800.0, screen_point.y / 600.0, -1.0); + let far_ndc_point = Point3::new(screen_point.x / 800.0, screen_point.y / 600.0, 1.0); + + // Unproject them to view-space. + let near_view_point = projection.unproject_point(&near_ndc_point); + let far_view_point = projection.unproject_point(&far_ndc_point); + + // Compute the view-space line parameters. + let line_location = near_view_point; + let line_direction = Unit::new_normalize(far_view_point - near_view_point); +} diff --git a/examples/transform_conversion.rs b/examples/transform_conversion.rs new file mode 100644 index 00000000..37b266c4 --- /dev/null +++ b/examples/transform_conversion.rs @@ -0,0 +1,23 @@ +extern crate nalgebra as na; + +use na::{Vector2, Isometry2, Similarity2}; + +fn main() { + // Isometry -> Similarity conversion always succeeds. + let iso = Isometry2::new(Vector2::new(1.0f32, 2.0), na::zero()); + let _: Similarity2 = na::convert(iso); + + // Similarity -> Isometry conversion fails if the scaling factor is not 1.0. + let sim_without_scaling = Similarity2::new(Vector2::new(1.0f32, 2.0), 3.14, 1.0); + let sim_with_scaling = Similarity2::new(Vector2::new(1.0f32, 2.0), 3.14, 2.0); + + let iso_success: Option> = na::try_convert(sim_without_scaling); + let iso_fail: Option> = na::try_convert(sim_with_scaling); + + assert!(iso_success.is_some()); + assert!(iso_fail.is_none()); + + // Similarity -> Isometry conversion can be forced at your own risks. + let iso_forced: Isometry2 = unsafe { na::convert_unchecked(sim_with_scaling) }; + assert_eq!(iso_success.unwrap(), iso_forced); +} diff --git a/examples/transform_matrix4.rs b/examples/transform_matrix4.rs new file mode 100644 index 00000000..7d473b6b --- /dev/null +++ b/examples/transform_matrix4.rs @@ -0,0 +1,39 @@ +#[macro_use] +extern crate approx; +extern crate alga; +extern crate nalgebra as na; + +use alga::linear::Transformation; +use na::{Vector3, Point3, Matrix4}; + + +fn main() { + // Create a uniform scaling matrix with scaling factor 2. + let mut m = Matrix4::new_scaling(2.0); + + assert_eq!(m.transform_vector(&Vector3::x()), Vector3::x() * 2.0); + assert_eq!(m.transform_vector(&Vector3::y()), Vector3::y() * 2.0); + assert_eq!(m.transform_vector(&Vector3::z()), Vector3::z() * 2.0); + + // Append a nonuniform scaling in-place. + m.append_nonuniform_scaling_mut(&Vector3::new(1.0, 2.0, 3.0)); + + assert_eq!(m.transform_vector(&Vector3::x()), Vector3::x() * 2.0); + assert_eq!(m.transform_vector(&Vector3::y()), Vector3::y() * 4.0); + assert_eq!(m.transform_vector(&Vector3::z()), Vector3::z() * 6.0); + + // Append a translation out-of-place. + let m2 = m.append_translation(&Vector3::new(42.0, 0.0, 0.0)); + + assert_eq!(m2.transform_point(&Point3::new(1.0, 1.0, 1.0)), Point3::new(42.0 + 2.0, 4.0, 6.0)); + + // Create rotation. + let rot = Matrix4::from_scaled_axis(&Vector3::x() * 3.14); + let rot_then_m = m * rot; // Right-multiplication is equivalent to prepending `rot` to `m`. + let m_then_rot = rot * m; // Left-multiplication is equivalent to appending `rot` to `m`. + + let pt = Point3::new(1.0, 2.0, 3.0); + + assert_relative_eq!(m.transform_point(&rot.transform_point(&pt)), rot_then_m.transform_point(&pt)); + assert_relative_eq!(rot.transform_point(&m.transform_point(&pt)), m_then_rot.transform_point(&pt)); +} diff --git a/examples/transform_vector_point.rs b/examples/transform_vector_point.rs new file mode 100644 index 00000000..f94b88e3 --- /dev/null +++ b/examples/transform_vector_point.rs @@ -0,0 +1,20 @@ +#[macro_use] +extern crate approx; +extern crate nalgebra as na; + +use std::f32; +use na::{Vector2, Point2, Isometry2}; + +fn main() { + let t = Isometry2::new(Vector2::new(1.0, 1.0), f32::consts::PI); + let p = Point2::new(1.0, 0.0); // Will be affected by te rotation and the translation. + let v = Vector2::x(); // Will *not* be affected by the translation. + + assert_relative_eq!(t * p, Point2::new(-1.0 + 1.0, 1.0)); + // ^^^^ │ ^^^^^^^^ + // rotated │ translated + + assert_relative_eq!(t * v, Vector2::new(-1.0, 0.0)); + // ^^^^^ + // rotated only +} diff --git a/examples/transform_vector_point3.rs b/examples/transform_vector_point3.rs new file mode 100644 index 00000000..f1a010bf --- /dev/null +++ b/examples/transform_vector_point3.rs @@ -0,0 +1,27 @@ +extern crate alga; +extern crate nalgebra as na; + +use alga::linear::Transformation; +use na::{Vector3, Vector4, Point3, Matrix4}; + + +fn main() { + let mut m = Matrix4::new_rotation_wrt_point(Vector3::x() * 1.57, Point3::new(1.0, 2.0, 1.0)); + m.append_scaling_mut(2.0); + + let point1 = Point3::new(2.0, 3.0, 4.0); + let homogeneous_point2 = Vector4::new(2.0, 3.0, 4.0, 1.0); + + // First option: use the dedicated `.transform_point(...)` method. + let transformed_point1 = m.transform_point(&point1); + // Second option: use the homogeneous coordinates of the point. + let transformed_homogeneous_point2 = m * homogeneous_point2; + + // Recover the 3D point from its 4D homogeneous coordinates. + let transformed_point2 = Point3::from_homogeneous(transformed_homogeneous_point2); + + // Check that transforming the 3D point with the `.transform_point` method is + // indeed equivalent to multiplying its 4D homogeneous coordinates by the 4x4 + // matrix. + assert_eq!(transformed_point1, transformed_point2.unwrap()); +} diff --git a/examples/transformation_pointer.rs b/examples/transformation_pointer.rs new file mode 100644 index 00000000..68d35e09 --- /dev/null +++ b/examples/transformation_pointer.rs @@ -0,0 +1,25 @@ +extern crate nalgebra as na; + +use na::{Vector3, Isometry3}; + +fn main() { + let iso = Isometry3::new(Vector3::new(1.0f32, 0.0, 1.0), na::zero()); + + // Compute the homogeneous coordinates first. + let iso_matrix = iso.to_homogeneous(); + let iso_array = iso_matrix.as_slice(); + let iso_pointer = iso_array.as_ptr(); + + /* Then pass the raw pointer to some graphics API. */ + + unsafe { + assert_eq!(*iso_pointer, 1.0); + assert_eq!(*iso_pointer.offset(5), 1.0); + assert_eq!(*iso_pointer.offset(10), 1.0); + assert_eq!(*iso_pointer.offset(15), 1.0); + + assert_eq!(*iso_pointer.offset(12), 1.0); + assert_eq!(*iso_pointer.offset(13), 0.0); + assert_eq!(*iso_pointer.offset(14), 1.0); + } +} diff --git a/examples/unit_wrapper.rs b/examples/unit_wrapper.rs new file mode 100644 index 00000000..dc898c50 --- /dev/null +++ b/examples/unit_wrapper.rs @@ -0,0 +1,30 @@ +extern crate nalgebra as na; + +use na::{Unit, Vector3}; + +fn length_on_direction_with_unit(v: &Vector3, dir: &Unit>) -> f32 { + // No need to normalize `dir`: we know that it is non-zero and normalized. + v.dot(dir.as_ref()) +} + + + +fn length_on_direction_without_unit(v: &Vector3, dir: &Vector3) -> f32 { + // Obligatory normalization of the direction vector (and test, for robustness). + if let Some(unit_dir) = dir.try_normalize(1.0e-6) { + v.dot(&unit_dir) + } + else { + // Normalization failed because the norm was too small. + panic!("Invalid input direction.") + } +} + +fn main() { + let v = Vector3::new(1.0, 2.0, 3.0); + + let l1 = length_on_direction_with_unit(&v, &Vector3::y_axis()); + let l2 = length_on_direction_without_unit(&v, &Vector3::y()); + + assert_eq!(l1, l2) +} diff --git a/src/core/alias.rs b/src/core/alias.rs index e4395dcd..df296257 100644 --- a/src/core/alias.rs +++ b/src/core/alias.rs @@ -19,52 +19,88 @@ pub type MatrixNM = Matrix>; /// A staticaly sized column-major square matrix with `D` rows and columns. pub type MatrixN = MatrixNM; +/// A stack-allocated, column-major, 1x1 square matrix. pub type Matrix1 = MatrixN; +/// A stack-allocated, column-major, 2x2 square matrix. pub type Matrix2 = MatrixN; +/// A stack-allocated, column-major, 3x3 square matrix. pub type Matrix3 = MatrixN; +/// A stack-allocated, column-major, 4x4 square matrix. pub type Matrix4 = MatrixN; +/// A stack-allocated, column-major, 5x5 square matrix. pub type Matrix5 = MatrixN; +/// A stack-allocated, column-major, 6x6 square matrix. pub type Matrix6 = MatrixN; +/// A stack-allocated, column-major, 1x2 square matrix. pub type Matrix1x2 = MatrixNM; +/// A stack-allocated, column-major, 1x3 square matrix. pub type Matrix1x3 = MatrixNM; +/// A stack-allocated, column-major, 1x4 square matrix. pub type Matrix1x4 = MatrixNM; +/// A stack-allocated, column-major, 1x5 square matrix. pub type Matrix1x5 = MatrixNM; +/// A stack-allocated, column-major, 1x6 square matrix. pub type Matrix1x6 = MatrixNM; +/// A stack-allocated, column-major, 2x3 square matrix. pub type Matrix2x3 = MatrixNM; +/// A stack-allocated, column-major, 2x4 square matrix. pub type Matrix2x4 = MatrixNM; +/// A stack-allocated, column-major, 2x5 square matrix. pub type Matrix2x5 = MatrixNM; +/// A stack-allocated, column-major, 2x6 square matrix. pub type Matrix2x6 = MatrixNM; +/// A stack-allocated, column-major, 3x4 square matrix. pub type Matrix3x4 = MatrixNM; +/// A stack-allocated, column-major, 3x5 square matrix. pub type Matrix3x5 = MatrixNM; +/// A stack-allocated, column-major, 3x6 square matrix. pub type Matrix3x6 = MatrixNM; +/// A stack-allocated, column-major, 4x5 square matrix. pub type Matrix4x5 = MatrixNM; +/// A stack-allocated, column-major, 4x6 square matrix. pub type Matrix4x6 = MatrixNM; +/// A stack-allocated, column-major, 5x6 square matrix. pub type Matrix5x6 = MatrixNM; +/// A stack-allocated, column-major, 2x1 square matrix. pub type Matrix2x1 = MatrixNM; +/// A stack-allocated, column-major, 3x1 square matrix. pub type Matrix3x1 = MatrixNM; +/// A stack-allocated, column-major, 4x1 square matrix. pub type Matrix4x1 = MatrixNM; +/// A stack-allocated, column-major, 5x1 square matrix. pub type Matrix5x1 = MatrixNM; +/// A stack-allocated, column-major, 6x1 square matrix. pub type Matrix6x1 = MatrixNM; +/// A stack-allocated, column-major, 3x2 square matrix. pub type Matrix3x2 = MatrixNM; +/// A stack-allocated, column-major, 4x2 square matrix. pub type Matrix4x2 = MatrixNM; +/// A stack-allocated, column-major, 5x2 square matrix. pub type Matrix5x2 = MatrixNM; +/// A stack-allocated, column-major, 6x2 square matrix. pub type Matrix6x2 = MatrixNM; +/// A stack-allocated, column-major, 4x3 square matrix. pub type Matrix4x3 = MatrixNM; +/// A stack-allocated, column-major, 5x3 square matrix. pub type Matrix5x3 = MatrixNM; +/// A stack-allocated, column-major, 6x3 square matrix. pub type Matrix6x3 = MatrixNM; +/// A stack-allocated, column-major, 5x4 square matrix. pub type Matrix5x4 = MatrixNM; +/// A stack-allocated, column-major, 6x4 square matrix. pub type Matrix6x4 = MatrixNM; +/// A stack-allocated, column-major, 6x5 square matrix. pub type Matrix6x5 = MatrixNM; @@ -81,11 +117,17 @@ pub type DVector = Matrix>; /// A statically sized D-dimensional column vector. pub type VectorN = MatrixNM; +/// A stack-allocated, 1-dimensional column vector. pub type Vector1 = VectorN; +/// A stack-allocated, 2-dimensional column vector. pub type Vector2 = VectorN; +/// A stack-allocated, 3-dimensional column vector. pub type Vector3 = VectorN; +/// A stack-allocated, 4-dimensional column vector. pub type Vector4 = VectorN; +/// A stack-allocated, 5-dimensional column vector. pub type Vector5 = VectorN; +/// A stack-allocated, 6-dimensional column vector. pub type Vector6 = VectorN; @@ -102,9 +144,15 @@ pub type RowDVector = Matrix>; /// A statically sized D-dimensional row vector. pub type RowVectorN = MatrixNM; +/// A stack-allocated, 1-dimensional row vector. pub type RowVector1 = RowVectorN; +/// A stack-allocated, 2-dimensional row vector. pub type RowVector2 = RowVectorN; +/// A stack-allocated, 3-dimensional row vector. pub type RowVector3 = RowVectorN; +/// A stack-allocated, 4-dimensional row vector. pub type RowVector4 = RowVectorN; +/// A stack-allocated, 5-dimensional row vector. pub type RowVector5 = RowVectorN; +/// A stack-allocated, 6-dimensional row vector. pub type RowVector6 = RowVectorN; diff --git a/src/core/allocator.rs b/src/core/allocator.rs index 23bfd356..d0c0a416 100644 --- a/src/core/allocator.rs +++ b/src/core/allocator.rs @@ -1,3 +1,5 @@ +//! Abstract definition of a matrix data storage allocator. + use std::any::Any; use core::Scalar; @@ -15,6 +17,7 @@ use core::storage::{Storage, OwnedStorage}; /// Every allocator must be both static and dynamic. Though not all implementations may share the /// same `Buffer` type. pub trait Allocator: Any + Sized { + /// The type of buffer this allocator can instanciate. type Buffer: OwnedStorage; /// Allocates a buffer with the given number of rows and columns without initializing its content. diff --git a/src/core/cg.rs b/src/core/cg.rs index cfd40ac3..e409d006 100644 --- a/src/core/cg.rs +++ b/src/core/cg.rs @@ -25,7 +25,7 @@ impl SquareMatrix #[inline] pub fn new_scaling(scaling: N) -> Self { let mut res = Self::from_diagonal_element(scaling); - res[(D::dim(), D::dim())] = N::one(); + res[(D::dim() - 1, D::dim() - 1)] = N::one(); res } @@ -50,7 +50,7 @@ impl SquareMatrix SB: Storage, U1>, S::Alloc: Allocator, U1> { let mut res = Self::one(); - res.fixed_slice_mut::, U1>(0, D::dim()).copy_from(translation); + res.fixed_slice_mut::, U1>(0, D::dim() - 1).copy_from(translation); res } @@ -311,7 +311,7 @@ impl SquareMatrix S::Alloc: Allocator, U1> { for i in 0 .. D::dim() { for j in 0 .. D::dim() - 1 { - self[(j, i)] += shift[i] * self[(D::dim(), j)]; + self[(j, i)] += shift[j] * self[(D::dim() - 1, i)]; } } } @@ -324,12 +324,12 @@ impl SquareMatrix S::Alloc: Allocator, U1> + Allocator, DimNameDiff> + Allocator> { - let scale = self.fixed_slice::>(D::dim(), 0).tr_dot(&shift); + let scale = self.fixed_slice::>(D::dim() - 1, 0).tr_dot(&shift); let post_translation = self.fixed_slice::, DimNameDiff>(0, 0) * shift; - self[(D::dim(), D::dim())] += scale; + self[(D::dim() - 1, D::dim() - 1)] += scale; - let mut translation = self.fixed_slice_mut::, U1>(0, D::dim()); + let mut translation = self.fixed_slice_mut::, U1>(0, D::dim() - 1); translation += post_translation; } } @@ -349,7 +349,7 @@ impl Transformation, SB>> for Squa fn transform_vector(&self, v: &ColumnVector, SB>) -> ColumnVector, SB> { let transform = self.fixed_slice::, DimNameDiff>(0, 0); - let normalizer = self.fixed_slice::>(D::dim(), 0); + let normalizer = self.fixed_slice::>(D::dim() - 1, 0); let n = normalizer.tr_dot(&v); if !n.is_zero() { @@ -363,9 +363,9 @@ impl Transformation, SB>> for Squa fn transform_point(&self, pt: &PointBase, SB>) -> PointBase, SB> { let transform = self.fixed_slice::, DimNameDiff>(0, 0); - let translation = self.fixed_slice::, U1>(0, D::dim()); - let normalizer = self.fixed_slice::>(D::dim(), 0); - let n = normalizer.tr_dot(&pt.coords) + unsafe { *self.get_unchecked(D::dim(), D::dim()) }; + let translation = self.fixed_slice::, U1>(0, D::dim() - 1); + let normalizer = self.fixed_slice::>(D::dim() - 1, 0); + let n = normalizer.tr_dot(&pt.coords) + unsafe { *self.get_unchecked(D::dim() - 1, D::dim() - 1) }; if !n.is_zero() { return transform * (pt / n) + translation; diff --git a/src/core/constraint.rs b/src/core/constraint.rs index 182bf2ae..0f017e5b 100644 --- a/src/core/constraint.rs +++ b/src/core/constraint.rs @@ -1,6 +1,8 @@ +//! Compatibility constraints between matrix shapes, e.g., for addition or multiplication. + use core::dimension::{Dim, DimName, Dynamic}; -/// A type for enforcing constraints. +/// A type used in `where` clauses for enforcing constraints. pub struct ShapeConstraint; /// Constraints `C1` and `R2` to be equivalent. @@ -14,9 +16,12 @@ where ShapeConstraint: DimEq { } macro_rules! equality_trait_decl( - ($($Trait: ident),* $(,)*) => {$( + ($($doc: expr, $Trait: ident),* $(,)*) => {$( // XXX: we can't do something like `DimEq for D2` because we would require a blancket impl… + #[doc = $doc] pub trait $Trait { + /// This is either equal to `D1` or `D2`, always choosing the one (if any) which is a type-level + /// constant. type Representative: Dim; } @@ -34,11 +39,26 @@ macro_rules! equality_trait_decl( )*} ); -equality_trait_decl!(DimEq, SameNumberOfRows, SameNumberOfColumns); +equality_trait_decl!( + "Constraints `D1` and `D2` to be equivalent.", + DimEq, -/// Constraints D1 and D2 to be equivalent, where the both designates dimensions of algebraic + "Constraints `D1` and `D2` to be equivalent. \ + They are both assumed to be the number of \ + rows of a matrix.", + SameNumberOfRows, + + "Constraints `D1` and `D2` to be equivalent. \ + They are both assumed to be the number of \ + columns of a matrix.", + SameNumberOfColumns +); + +/// Constraints D1 and D2 to be equivalent, where they both designate dimensions of algebraic /// entities (e.g. square matrices). pub trait SameDimension: SameNumberOfRows + SameNumberOfColumns { + /// This is either equal to `D1` or `D2`, always choosing the one (if any) which is a type-level + /// constant. type Representative: Dim; } diff --git a/src/core/construction.rs b/src/core/construction.rs index db586a6c..606f0a2f 100644 --- a/src/core/construction.rs +++ b/src/core/construction.rs @@ -116,6 +116,10 @@ impl> Matrix res } + /// Builds a new matrix from its rows. + /// + /// Panics if not enough rows are provided (for statically-sized matrices), or if all rows do + /// not have the same dimensions. #[inline] pub fn from_rows(rows: &[Matrix]) -> Matrix where SB: Storage { @@ -127,13 +131,18 @@ impl> Matrix if C::try_to_usize().is_none() { assert!(rows.iter().all(|r| r.len() == ncols), - "The rows provided must all have the same dimension."); + "The provided rows must all have the same dimension."); } // FIXME: optimize that. Self::from_fn_generic(R::from_usize(nrows), C::from_usize(ncols), |i, j| rows[i][(0, j)]) } + + /// Builds a new matrix from its columns. + /// + /// Panics if not enough columns are provided (for statically-sized matrices), or if all + /// columns do not have the same dimensions. #[inline] pub fn from_columns(columns: &[ColumnVector]) -> Matrix where SB: Storage { @@ -239,12 +248,17 @@ macro_rules! impl_constructors( Self::from_fn_generic($($gargs, )* f) } + /// Creates an identity matrix. If the matrix is not square, the largest square + /// submatrix (starting at the first row and column) is set to the identity while all + /// other entries are set to zero. #[inline] pub fn identity($($args: usize,)*) -> Matrix where N: Zero + One { Self::identity_generic($($gargs),* ) } + /// Creates a matrix filled with its diagonal filled with `elt` and all other + /// components set to zero. #[inline] pub fn from_diagonal_element($($args: usize,)* elt: N) -> Matrix where N: Zero + One { diff --git a/src/core/coordinates.rs b/src/core/coordinates.rs index 6fff9193..3a8f371f 100644 --- a/src/core/coordinates.rs +++ b/src/core/coordinates.rs @@ -1,3 +1,9 @@ +#![allow(missing_docs)] + +//! Structures to which matrices and vector can be auto-dereferenced (through `Deref`) to access +//! components using their names. For example, if `v` is a 3D vector, one can write `v.z` instead +//! of `v[2]`. + use std::mem; use std::ops::{Deref, DerefMut}; @@ -14,8 +20,10 @@ use core::allocator::OwnedAllocator; macro_rules! coords_impl( ($T: ident; $($comps: ident),*) => { + /// Data structure used to provide access to matrix and vector coordinates with the dot + /// notation, e.g., `v.x` is the same as `v[0]` for a vector. #[repr(C)] - #[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] + #[derive(Eq, PartialEq, Clone, Hash, Debug, Copy, Serialize, Deserialize)] pub struct $T { $(pub $comps: N),* } diff --git a/src/core/decompositions.rs b/src/core/decompositions.rs index 7c19bd21..41183fcb 100644 --- a/src/core/decompositions.rs +++ b/src/core/decompositions.rs @@ -272,14 +272,7 @@ impl> SquareMatrix { /// /// Matrix symmetricness is not checked. Returns `None` if `self` is not definite positive. #[inline] - pub fn cholesky(&self) -> Option> { - let out = self.transpose(); - self.do_cholesky(out).ok() - } - - /// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T - #[inline] - pub fn cholesky_unchecked(&self) -> Result, &'static str> { + pub fn cholesky(&self) -> Result, &'static str> { let out = self.transpose(); if !out.relative_eq(self, N::default_epsilon(), N::default_max_relative()) { @@ -289,6 +282,13 @@ impl> SquareMatrix { self.do_cholesky(out) } + /// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T + #[inline] + pub fn cholesky_unchecked(&self) -> Result, &'static str> { + let out = self.transpose(); + self.do_cholesky(out) + } + #[inline(always)] fn do_cholesky(&self, mut out: OwnedSquareMatrix) -> Result, &'static str> { diff --git a/src/core/default_allocator.rs b/src/core/default_allocator.rs index 41cdf50d..acfbe97c 100644 --- a/src/core/default_allocator.rs +++ b/src/core/default_allocator.rs @@ -1,3 +1,8 @@ +//! The default matrix data storage allocator. +//! +//! This will use stack-allocated buffers for matrices with dimensions known at compile-time, and +//! heap-allocated buffers for matrices with at least one dimension unknown at compile-time. + use std::mem; use std::ops::Mul; diff --git a/src/core/dimension.rs b/src/core/dimension.rs index 1f723ad4..20819b68 100644 --- a/src/core/dimension.rs +++ b/src/core/dimension.rs @@ -1,10 +1,14 @@ +#![allow(missing_docs)] + +//! Traits and tags for identifying the dimension of all algebraic entities. + use std::fmt::Debug; use std::any::Any; use std::ops::{Add, Sub, Mul, Div}; use typenum::{self, Unsigned, UInt, B1, Bit, UTerm, Sum, Prod, Diff, Quot}; /// Dim of dynamically-sized algebraic entities. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] pub struct Dynamic { value: usize } @@ -19,17 +23,27 @@ impl Dynamic { } } -/// Trait implemented by dynamic dimensions. +/// Trait implemented by `Dynamic`. pub trait IsDynamic { } -/// Trait implemented by dimensions that are not equal to U1. +/// Trait implemented by `Dynamic` and type-level integers different from `U1`. pub trait IsNotStaticOne { } impl IsDynamic for Dynamic { } impl IsNotStaticOne for Dynamic { } +/// Trait implemented by any type that can be used as a dimension. This includes type-level +/// integers and `Dynamic` (for dimensions not known at compile-time). pub trait Dim: Any + Debug + Copy + PartialEq + Send { + /// Gets the compile-time value of `Self`. Returns `None` if it is not known, i.e., if `Self = + /// Dynamic`. fn try_to_usize() -> Option; + + /// Gets the run-time value of `self`. For type-level integers, this is the same as + /// `Self::try_to_usize().unwrap()`. fn value(&self) -> usize; + + /// Builds an instance of `Self` from a run-time value. Panics if `Self` is a type-level + /// integer and `dim != Self::try_to_usize().unwrap()`. fn from_usize(dim: usize) -> Self; } @@ -127,6 +141,7 @@ dim_ops!( ); +/// Trait implemented exclusively by type-level integers. pub trait DimName: Dim { type Value: NamedDim; @@ -146,7 +161,7 @@ pub trait NamedDim: Sized + Any + Unsigned { type Name: DimName; } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct U1; impl Dim for U1 { @@ -182,7 +197,7 @@ impl NamedDim for typenum::U1{ macro_rules! named_dimension( ($($D: ident),* $(,)*) => {$( - #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct $D; impl Dim for $D { diff --git a/src/core/iter.rs b/src/core/iter.rs index 50af9da5..3c99a4ae 100644 --- a/src/core/iter.rs +++ b/src/core/iter.rs @@ -1,3 +1,5 @@ +//! Matrix iterators. + use std::marker::PhantomData; use std::mem; diff --git a/src/core/matrix.rs b/src/core/matrix.rs index cde94f38..c92b76a8 100644 --- a/src/core/matrix.rs +++ b/src/core/matrix.rs @@ -7,9 +7,9 @@ use std::any::TypeId; use std::mem; use approx::ApproxEq; -use alga::general::{Field, Real}; +use alga::general::{Ring, Real}; -use core::Scalar; +use core::{Scalar, Unit}; use core::dimension::{Dim, DimAdd, DimSum, U1, U2}; use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; use core::iter::{MatrixIter, MatrixIterMut}; @@ -55,10 +55,37 @@ pub type MatrixTrMul = Matrix = Matrix>::Alloc as Allocator>::Buffer>; +/// The most generic column-major matrix (and vector) type. +/// +/// It combines four type parameters: +/// - `N`: for the matrix components scalar type. +/// - `R`: for the matrix number of rows. +/// - `C`: for the matrix number of columns. +/// - `S`: for the matrix data storage, i.e., the buffer that actually contains the matrix +/// components. +/// +/// The matrix dimensions parameters `R` and `C` can either be: +/// - type-level unsigned integer contants (e.g. `U1`, `U124`) from the `nalgebra::` root module. +/// All numbers from 0 to 127 are defined that way. +/// - type-level unsigned integer constants (e.g. `U1024`, `U10000`) from the `typenum::` crate. +/// Using those, you will not get error messages as nice as for numbers smaller than 128 defined on +/// the `nalgebra::` module. +/// - the special value `Dynamic` from the `nalgebra::` root module. This indicates that the +/// specified dimension is not known at compile-time. Note that this will generally imply that the +/// matrix data storage `S` performs a dynamic allocation and contains extra metadata for the +/// matrix shape. +/// +/// Note that mixing `Dynamic` with type-level unsigned integers is allowed. Actually, a +/// dynamically-sized column vector should be represented as a `Matrix` (given +/// some concrete types for `N` and a compatible data storage type `S`). #[repr(C)] -#[derive(RustcEncodable, RustcDecodable, Hash, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy)] pub struct Matrix { + /// The data storage that contains all the matrix components and informations about its number + /// of rows and column (if needed). pub data: S, + + #[serde(skip_serializing, skip_deserializing)] _phantoms: PhantomData<(N, R, C)> } @@ -149,7 +176,7 @@ impl> Matrix { nrows * ncols } - /// The shape (number of rows, number of columns) of this matrix. + /// The shape of this matrix returned as the tuple (number of rows, number of columns). #[inline] pub fn shape(&self) -> (usize, usize) { let (nrows, ncols) = self.data.shape(); @@ -405,6 +432,20 @@ impl ColumnVector res } + + /// Constructs a vector from coordinates in projective space, i.e., removes a `0` at the end of + /// `self`. Returns `None` if this last component is not zero. + #[inline] + pub fn from_homogeneous(v: ColumnVector, SB>) -> Option> + where SB: Storage, U1, Alloc = S::Alloc> { + if v[v.len() - 1].is_zero() { + let nrows = D::from_usize(v.len() - 1); + Some(v.generic_slice((0, 0), (nrows, U1)).into_owned()) + } + else { + None + } + } } @@ -677,7 +718,7 @@ impl PartialOrd for Matrix if let Some(mut first_ord) = first_ord { let mut it = self.iter().zip(other.iter()); - it.next(); // Drop the first elements (we already tested it). + let _ = it.next(); // Drop the first elements (we already tested it). for (left, right) in it { if let Some(ord) = left.partial_cmp(right) { @@ -806,7 +847,7 @@ impl fmt::Display for Matrix impl Matrix - where N: Scalar + Field, + where N: Scalar + Ring, S: Storage { /// The dot product between two matrices (seen as vectors). #[inline] @@ -1011,3 +1052,35 @@ impl Matrix } } } + +impl ApproxEq for Unit> + where N: Scalar + ApproxEq, + S: Storage, + N::Epsilon: Copy { + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.as_ref().relative_eq(other.as_ref(), epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) + } +} diff --git a/src/core/matrix_alga.rs b/src/core/matrix_alga.rs index 0c4ae645..ed0bad14 100644 --- a/src/core/matrix_alga.rs +++ b/src/core/matrix_alga.rs @@ -263,7 +263,7 @@ impl FiniteDimInnerSpace for Matrix a = Self::from_column_slice(&[N::zero(), -v[2], v[1]]); }; - a.normalize_mut(); + let _ = a.normalize_mut(); if f(&a.cross(v)) { f(&a); diff --git a/src/core/matrix_array.rs b/src/core/matrix_array.rs index 6d2e7154..5ef36198 100644 --- a/src/core/matrix_array.rs +++ b/src/core/matrix_array.rs @@ -1,6 +1,11 @@ +use std::mem; +use std::marker::PhantomData; use std::ops::{Deref, DerefMut, Mul}; -use std::fmt::{Debug, Formatter, Result}; +use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; +use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde::ser::SerializeSeq; +use serde::de::{SeqVisitor, Visitor}; use typenum::Prod; use generic_array::{ArrayLength, GenericArray}; @@ -28,6 +33,7 @@ where R: DimName, data: GenericArray> } + impl Hash for MatrixArray where N: Hash, R: DimName, @@ -70,7 +76,7 @@ where N: Debug, R::Value: Mul, Prod: ArrayLength { #[inline] - fn fmt(&self, fmt: &mut Formatter) -> Result { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { self.data.fmt(fmt) } } @@ -185,3 +191,97 @@ unsafe impl OwnedStorage for MatrixArray &mut self[..] } } + + +/* + * + * Allocation-less serde impls. + * + */ +// XXX: open an issue for GenericArray so that it implements serde traits? +impl Serialize for MatrixArray +where N: Scalar + Serialize, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + + + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + let mut serializer = serializer.serialize_seq_fixed_size(R::dim() * C::dim())?; + + for e in self.iter() { + serializer.serialize_element(e)?; + } + + serializer.end() + } +} + + +impl Deserialize for MatrixArray +where N: Scalar + Deserialize, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + + + fn deserialize(deserializer: D) -> Result + where D: Deserializer { + + let len = R::dim() * C::dim(); + deserializer.deserialize_seq_fixed_size(len, MatrixArrayVisitor::new()) + } +} + + +/// A visitor that produces a matrix array. +struct MatrixArrayVisitor { + marker: PhantomData<(N, R, C)> +} + +impl MatrixArrayVisitor +where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + + /// Construct a new sequence visitor. + pub fn new() -> Self { + MatrixArrayVisitor { + marker: PhantomData, + } + } +} + +impl Visitor for MatrixArrayVisitor +where N: Scalar + Deserialize, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength { + + type Value = MatrixArray; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a matrix array") + } + + #[inline] + fn visit_seq(self, mut visitor: V) -> Result, V::Error> + where V: SeqVisitor { + + let mut out: Self::Value = unsafe { mem::uninitialized() }; + let mut curr = 0; + + while let Some(value) = try!(visitor.visit()) { + out[curr] = value; + curr += 1; + } + + Ok(out) + } +} diff --git a/src/core/matrix_slice.rs b/src/core/matrix_slice.rs index 0008c6bc..8a09fb5f 100644 --- a/src/core/matrix_slice.rs +++ b/src/core/matrix_slice.rs @@ -7,7 +7,8 @@ use core::storage::{Storage, StorageMut, Owned}; use core::allocator::Allocator; macro_rules! slice_storage_impl( - ($Storage: ident as $SRef: ty; $T: ident.$get_addr: ident ($Ptr: ty as $Ref: ty)) => { + ($doc: expr; $Storage: ident as $SRef: ty; $T: ident.$get_addr: ident ($Ptr: ty as $Ref: ty)) => { + #[doc = $doc] pub struct $T<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> { ptr: $Ptr, shape: (R, C), @@ -54,8 +55,14 @@ macro_rules! slice_storage_impl( } ); -slice_storage_impl!(Storage as &'a S; SliceStorage.get_address_unchecked(*const N as &'a N)); -slice_storage_impl!(StorageMut as &'a mut S; SliceStorageMut.get_address_unchecked_mut(*mut N as &'a mut N)); +slice_storage_impl!("A matrix data storage for a matrix slice. Only contains an internal reference \ + to another matrix data storage."; + Storage as &'a S; SliceStorage.get_address_unchecked(*const N as &'a N)); + +slice_storage_impl!("A mutable matrix data storage for mutable matrix slice. Only contains an \ + internal mutable reference to another matrix data storage."; + StorageMut as &'a mut S; SliceStorageMut.get_address_unchecked_mut(*mut N as &'a mut N) +); impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Copy @@ -345,6 +352,11 @@ macro_rules! matrix_slice_impl( } } + + /// Slices this matrix starting at its component `(start.0, start.1)` and with + /// `(shape.0, shape.1)` components. Each row (resp. column) of the sliced matrix is + /// separated by `steps.0` (resp. `steps.1`) ignored rows (resp. columns) of the + /// original matrix. #[inline] pub fn $slice_with_steps($me: $Me, start: (usize, usize), shape: (usize, usize), steps: (usize, usize)) -> $MatrixSlice { @@ -371,6 +383,10 @@ macro_rules! matrix_slice_impl( } } + /// Slices this matrix starting at its component `(start.0, start.1)` and with + /// `(R::dim(), CSlice::dim())` components. Each row (resp. column) of the sliced + /// matrix is separated by `steps.0` (resp. `steps.1`) ignored rows (resp. columns) of + /// the original matrix. #[inline] pub fn $fixed_slice_with_steps($me: $Me, start: (usize, usize), steps: (usize, usize)) -> $MatrixSlice diff --git a/src/core/matrix_vec.rs b/src/core/matrix_vec.rs index 2393a15d..b492ec47 100644 --- a/src/core/matrix_vec.rs +++ b/src/core/matrix_vec.rs @@ -5,7 +5,6 @@ use core::dimension::{Dim, DimName, Dynamic, U1}; use core::storage::{Storage, StorageMut, Owned, OwnedStorage}; use core::default_allocator::DefaultAllocator; - /* * * Storage. @@ -13,7 +12,7 @@ use core::default_allocator::DefaultAllocator; */ /// A Vec-based matrix data storage. It may be dynamically-sized. #[repr(C)] -#[derive(Eq, Debug, Clone, PartialEq)] +#[derive(Eq, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MatrixVec { data: Vec, nrows: R, @@ -21,8 +20,10 @@ pub struct MatrixVec { } impl MatrixVec { + /// Creates a new dynamic matrix data storage from the given vector and shape. #[inline] pub fn new(nrows: R, ncols: C, data: Vec) -> MatrixVec { + assert!(nrows.value() * ncols.value() == data.len(), "Data storage buffer dimension mismatch."); MatrixVec { data: data, nrows: nrows, diff --git a/src/core/mod.rs b/src/core/mod.rs index e56db888..f37ec9b8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,9 +1,11 @@ +//! Data structures for vector and matrix computations. + pub mod dimension; pub mod constraint; pub mod allocator; pub mod storage; pub mod coordinates; -pub mod ops; +mod ops; pub mod iter; pub mod default_allocator; diff --git a/src/core/ops.rs b/src/core/ops.rs index ebc444cf..f310f68b 100644 --- a/src/core/ops.rs +++ b/src/core/ops.rs @@ -33,7 +33,9 @@ impl Index<(usize, usize)> for Matrix #[inline] fn index(&self, ij: (usize, usize)) -> &N { - assert!(ij < self.shape(), "Matrix index out of bounds."); + let shape = self.shape(); + assert!(ij.0 < shape.0 && ij.1 < shape.1, "Matrix index out of bounds."); + unsafe { self.get_unchecked(ij.0, ij.1) } } } @@ -53,7 +55,9 @@ impl IndexMut<(usize, usize)> for Matrix #[inline] fn index_mut(&mut self, ij: (usize, usize)) -> &mut N { - assert!(ij < self.shape(), "Matrix index out of bounds."); + let shape = self.shape(); + assert!(ij.0 < shape.0 && ij.1 < shape.1, "Matrix index out of bounds."); + unsafe { self.get_unchecked_mut(ij.0, ij.1) } } } @@ -121,7 +125,13 @@ macro_rules! componentwise_binop_impl( assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); let mut res = self.into_owned_sum::(); - for (left, right) in res.iter_mut().zip(right.iter()) { + // XXX: optimize our iterator! + // + // Using our own iterator prevents loop unrolling, wich breaks some optimization + // (like SIMD). On the other hand, using the slice iterator is 4x faster. + + // for (left, right) in res.iter_mut().zip(right.iter()) { + for (left, right) in res.as_mut_slice().iter_mut().zip(right.iter()) { *left = left.$method(*right) } @@ -143,7 +153,13 @@ macro_rules! componentwise_binop_impl( assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); let mut res = right.into_owned_sum::(); - for (left, right) in self.iter().zip(res.iter_mut()) { + // XXX: optimize our iterator! + // + // Using our own iterator prevents loop unrolling, wich breaks some optimization + // (like SIMD). On the other hand, using the slice iterator is 4x faster. + + // for (left, right) in self.iter().zip(res.iter_mut()) { + for (left, right) in self.iter().zip(res.as_mut_slice().iter_mut()) { *right = left.$method(*right) } @@ -237,7 +253,13 @@ macro_rules! componentwise_scalarop_impl( fn $method(self, rhs: N) -> Self::Output { let mut res = self.into_owned(); - for left in res.iter_mut() { + // XXX: optimize our iterator! + // + // Using our own iterator prevents loop unrolling, wich breaks some optimization + // (like SIMD). On the other hand, using the slice iterator is 4x faster. + + // for left in res.iter_mut() { + for left in res.as_mut_slice().iter_mut() { *left = left.$method(rhs) } @@ -282,7 +304,13 @@ macro_rules! left_scalar_mul_impl( fn mul(self, right: Matrix<$T, R, C, S>) -> Self::Output { let mut res = right.into_owned(); - for right in res.iter_mut() { + // XXX: optimize our iterator! + // + // Using our own iterator prevents loop unrolling, wich breaks some optimization + // (like SIMD). On the other hand, using the slice iterator is 4x faster. + + // for right in res.iter_mut() { + for right in res.as_mut_slice().iter_mut() { *right = self * *right } diff --git a/src/core/storage.rs b/src/core/storage.rs index a3f98a43..d70e416c 100644 --- a/src/core/storage.rs +++ b/src/core/storage.rs @@ -1,3 +1,5 @@ +//! Abstract definition of a matrix data storage. + use std::mem; use std::any::Any; @@ -110,6 +112,12 @@ pub unsafe trait Storage: Sized { } } + +/// Trait implemented by matrix data storage that can provide a mutable access to its elements. +/// +/// Note that a mutable access does not mean that the matrix owns its data. For example, a mutable +/// matrix slice can provide mutable access to its elements even if it does not own its data (it +/// contains only an internal reference to them). pub unsafe trait StorageMut: Storage { /// The matrix mutable data pointer. fn ptr_mut(&mut self) -> *mut N; diff --git a/src/core/unit.rs b/src/core/unit.rs index 9c35bed0..c8e8c578 100644 --- a/src/core/unit.rs +++ b/src/core/unit.rs @@ -1,4 +1,5 @@ -use std::ops::Neg; +use std::mem; +use std::ops::{Neg, Deref}; use approx::ApproxEq; use alga::general::SubsetOf; @@ -9,7 +10,7 @@ use alga::linear::NormedSpace; /// /// Use `.as_ref()` or `.unwrap()` to obtain the undelying value by-reference or by-move. #[repr(C)] -#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)] +#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy, Serialize, Deserialize)] pub struct Unit { value: T } @@ -114,34 +115,34 @@ where T::Field: ApproxEq { } } -impl ApproxEq for Unit { - type Epsilon = T::Epsilon; - - #[inline] - fn default_epsilon() -> Self::Epsilon { - T::default_epsilon() - } - - #[inline] - fn default_max_relative() -> Self::Epsilon { - T::default_max_relative() - } - - #[inline] - fn default_max_ulps() -> u32 { - T::default_max_ulps() - } - - #[inline] - fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { - self.value.relative_eq(&other.value, epsilon, max_relative) - } - - #[inline] - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - self.value.ulps_eq(&other.value, epsilon, max_ulps) - } -} +// impl ApproxEq for Unit { +// type Epsilon = T::Epsilon; +// +// #[inline] +// fn default_epsilon() -> Self::Epsilon { +// T::default_epsilon() +// } +// +// #[inline] +// fn default_max_relative() -> Self::Epsilon { +// T::default_max_relative() +// } +// +// #[inline] +// fn default_max_ulps() -> u32 { +// T::default_max_ulps() +// } +// +// #[inline] +// fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { +// self.value.relative_eq(&other.value, epsilon, max_relative) +// } +// +// #[inline] +// fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { +// self.value.ulps_eq(&other.value, epsilon, max_ulps) +// } +// } // FIXME:re-enable this impl when spacialization is possible. @@ -163,3 +164,12 @@ impl Neg for Unit { Unit::new_unchecked(-self.value) } } + +impl Deref for Unit { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + unsafe { mem::transmute(self) } + } +} diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 7b5fc6ee..5be41990 100644 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -11,14 +11,24 @@ use core::storage::{Storage, OwnedStorage}; use core::allocator::{Allocator, OwnedAllocator}; use geometry::{TranslationBase, PointBase}; + +/// An isometry that uses a data storage deduced from the allocator `A`. +pub type OwnedIsometryBase = + IsometryBase>::Buffer, R>; + /// A direct isometry, i.e., a rotation followed by a translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] +#[derive(Hash, Debug, Clone, Copy, Serialize, Deserialize)] pub struct IsometryBase { + /// The pure rotational part of this isometry. pub rotation: R, + /// The pure translational part of this isometry. pub translation: TranslationBase, - // One private field just to prevent explicit construction. - _noconstruct: PhantomData + + + // One dummy private field just to prevent explicit construction. + #[serde(skip_serializing, skip_deserializing)] + _noconstruct: PhantomData } impl IsometryBase diff --git a/src/geometry/isometry_ops.rs b/src/geometry/isometry_ops.rs index 02f924ed..0ca1bca8 100644 --- a/src/geometry/isometry_ops.rs +++ b/src/geometry/isometry_ops.rs @@ -214,8 +214,8 @@ isometry_binop_assign_impl_all!( ); -// IsometryBase × RotationBase -// IsometryBase ÷ RotationBase +// IsometryBase × R +// IsometryBase ÷ R isometry_binop_impl_all!( Mul, mul; self: IsometryBase, rhs: R, Output = IsometryBase; @@ -357,10 +357,10 @@ isometry_from_composition_impl_all!( Mul, mul; (D, D), (D, U1) for D: DimName; self: RotationBase, right: TranslationBase, Output = IsometryBase>; - [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); - [ref val] => IsometryBase::from_parts(TranslationBase::from_vector(self * right.vector), self.clone()); + [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); + [ref val] => IsometryBase::from_parts(TranslationBase::from_vector(self * right.vector), self.clone()); [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &right.vector), self); - [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector(self * &right.vector), self.clone()); + [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector(self * &right.vector), self.clone()); ); @@ -370,10 +370,10 @@ isometry_from_composition_impl_all!( (U4, U1), (U3, U1); self: UnitQuaternionBase, right: TranslationBase, Output = IsometryBase>; - [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); - [ref val] => IsometryBase::from_parts(TranslationBase::from_vector(self * right.vector), self.clone()); + [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); + [ref val] => IsometryBase::from_parts(TranslationBase::from_vector( self * right.vector), self.clone()); [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &right.vector), self); - [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector(self * &right.vector), self.clone()); + [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector( self * &right.vector), self.clone()); ); // RotationBase × IsometryBase diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index 21b51c61..0ee4c9e0 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -1,3 +1,5 @@ +//! Data structures for points and usual transformations (rotations, isometries, etc.) + mod op_macros; mod point; @@ -28,6 +30,7 @@ mod unit_complex; mod unit_complex_construction; mod unit_complex_ops; mod unit_complex_alga; +mod unit_complex_conversion; mod translation; mod translation_construction; diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index 18ebd9ed..900fd74a 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -13,7 +13,7 @@ use core::helper; use geometry::{PointBase, OwnedPoint}; /// A 3D orthographic projection stored as an homogeneous 4x4 matrix. -#[derive(Debug, Clone, Copy)] // FIXME: Hash +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] // FIXME: Hash pub struct OrthographicBase> { matrix: SquareMatrix } @@ -25,9 +25,7 @@ impl Eq for OrthographicBase where N: Scalar + Eq, S: Storage { } -impl PartialEq for OrthographicBase - where N: Scalar, - S: Storage { +impl> PartialEq for OrthographicBase { #[inline] fn eq(&self, right: &Self) -> bool { self.matrix == right.matrix @@ -80,10 +78,7 @@ impl OrthographicBase } } -impl OrthographicBase - where N: Real, - S: Storage { - +impl> OrthographicBase { /// A reference to the underlying homogeneous transformation matrix. #[inline] pub fn as_matrix(&self) -> &SquareMatrix { @@ -96,6 +91,26 @@ impl OrthographicBase self.matrix } + /// Retrieves the inverse of the underlying homogeneous matrix. + #[inline] + pub fn inverse(&self) -> OwnedSquareMatrix { + let mut res = self.to_homogeneous(); + + let inv_m11 = N::one() / self.matrix[(0, 0)]; + let inv_m22 = N::one() / self.matrix[(1, 1)]; + let inv_m33 = N::one() / self.matrix[(2, 2)]; + + res[(0, 0)] = inv_m11; + res[(1, 1)] = inv_m22; + res[(2, 2)] = inv_m33; + + res[(0, 3)] = -self.matrix[(0, 3)] * inv_m11; + res[(1, 3)] = -self.matrix[(1, 3)] * inv_m22; + res[(2, 3)] = -self.matrix[(2, 3)] * inv_m33; + + res + } + /// Computes the corresponding homogeneous matrix. #[inline] pub fn to_homogeneous(&self) -> OwnedSquareMatrix { @@ -151,6 +166,18 @@ impl OrthographicBase ) } + /// Un-projects a point. Faster than multiplication by the underlying matrix inverse. + #[inline] + pub fn unproject_point(&self, p: &PointBase) -> OwnedPoint + where SB: Storage { + + OwnedPoint::::new( + (p[0] - self.matrix[(0, 3)]) / self.matrix[(0, 0)], + (p[1] - self.matrix[(1, 3)]) / self.matrix[(1, 1)], + (p[2] - self.matrix[(2, 3)]) / self.matrix[(2, 2)] + ) + } + // FIXME: when we get specialization, specialize the Mul impl instead. /// Projects a vector. Faster than matrix multiplication. #[inline] @@ -165,9 +192,7 @@ impl OrthographicBase } } -impl OrthographicBase - where N: Real, - S: StorageMut { +impl> OrthographicBase { /// Sets the smallest x-coordinate of the view cuboid. #[inline] pub fn set_left(&mut self, left: N) { diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index 55bf59b5..c9110ea9 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -13,7 +13,7 @@ use core::helper; use geometry::{PointBase, OwnedPoint}; /// A 3D perspective projection stored as an homogeneous 4x4 matrix. -#[derive(Debug, Clone, Copy)] // FIXME: Hash +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] // FIXME: Hash pub struct PerspectiveBase> { matrix: SquareMatrix } @@ -85,6 +85,25 @@ impl PerspectiveBase self.matrix } + /// Retrieves the inverse of the underlying homogeneous matrix. + #[inline] + pub fn inverse(&self) -> OwnedSquareMatrix { + let mut res = self.to_homogeneous(); + + res[(0, 0)] = N::one() / self.matrix[(0, 0)]; + res[(1, 1)] = N::one() / self.matrix[(1, 1)]; + res[(2, 2)] = N::zero(); + + let m23 = self.matrix[(2, 3)]; + let m32 = self.matrix[(3, 2)]; + + res[(2, 3)] = N::one() / m32; + res[(3, 2)] = N::one() / m23; + res[(3, 3)] = -self.matrix[(2, 2)] / (m23 * m32); + + res + } + /// Computes the corresponding homogeneous matrix. #[inline] pub fn to_homogeneous(&self) -> OwnedSquareMatrix { @@ -137,6 +156,20 @@ impl PerspectiveBase ) } + /// Un-projects a point. Faster than multiplication by the matrix inverse. + #[inline] + pub fn unproject_point(&self, p: &PointBase) -> OwnedPoint + where SB: Storage { + + let inverse_denom = self.matrix[(2, 3)] / (p[2] + self.matrix[(2, 2)]); + + OwnedPoint::::new( + p[0] * inverse_denom / self.matrix[(0, 0)], + p[1] * inverse_denom / self.matrix[(1, 1)], + -inverse_denom + ) + } + // FIXME: when we get specialization, specialize the Mul impl instead. /// Projects a vector. Faster than matrix multiplication. #[inline] diff --git a/src/geometry/point.rs b/src/geometry/point.rs index e29158ee..70cba65f 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -9,13 +9,11 @@ use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; use core::storage::{Storage, StorageMut, MulStorage}; use core::allocator::{Allocator, SameShapeR}; -pub type PointSumStorage = - <>::Alloc as Allocator, U1>>::Buffer; - // XXX Bad name: we can't even add points… /// The type of the result of the sum of a point with a vector. pub type PointSum = - PointBase, PointSumStorage>; + PointBase, + <>::Alloc as Allocator, U1>>::Buffer>; /// The type of the result of the multiplication of a point by a matrix. pub type PointMul = PointBase>; @@ -23,10 +21,11 @@ pub type PointMul = PointBase = PointBase>::Buffer>; -/// A point in n-dimensional euclidean space. +/// A point in a n-dimensional euclidean space. #[repr(C)] -#[derive(Hash, Debug)] +#[derive(Hash, Debug, Serialize, Deserialize)] pub struct PointBase> { + /// The coordinates of this point, i.e., the shift from the origin. pub coords: ColumnVector } diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 9332dfa2..e970ae15 100644 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -21,8 +21,9 @@ pub type OwnedUnitQuaternionBase = UnitQuaternionBase` for a quaternion /// that may be used as a rotation. #[repr(C)] -#[derive(Hash, Debug, Copy, Clone)] +#[derive(Hash, Debug, Copy, Clone, Serialize, Deserialize)] pub struct QuaternionBase> { + /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. pub coords: ColumnVector } @@ -86,6 +87,12 @@ impl QuaternionBase self.coords.norm_squared() } + /// Normalizes this quaternion. + #[inline] + pub fn normalize(&self) -> OwnedQuaternionBase { + QuaternionBase::from_vector(self.coords.normalize()) + } + /// Compute the conjugate of this quaternion. #[inline] pub fn conjugate(&self) -> OwnedQuaternionBase { @@ -108,6 +115,13 @@ impl QuaternionBase None } } + + /// Linear interpolation between two quaternion. + #[inline] + pub fn lerp(&self, other: &QuaternionBase, t: N) -> OwnedQuaternionBase + where S2: Storage { + self * (N::one() - t) + other * t + } } @@ -208,6 +222,12 @@ impl QuaternionBase true } } + + /// Normalizes this quaternion. + #[inline] + pub fn normalize_mut(&mut self) -> N { + self.coords.normalize_mut() + } } impl ApproxEq for QuaternionBase @@ -262,6 +282,18 @@ pub type UnitQuaternionBase = Unit>; impl UnitQuaternionBase where N: Real, S: Storage { + /// Moves this unit quaternion into one that owns its data. + #[inline] + pub fn into_owned(self) -> OwnedUnitQuaternionBase { + UnitQuaternionBase::new_unchecked(self.unwrap().into_owned()) + } + + /// Clones this unit quaternion into one that owns its data. + #[inline] + pub fn clone_owned(&self) -> OwnedUnitQuaternionBase { + UnitQuaternionBase::new_unchecked(self.as_ref().clone_owned()) + } + /// The rotation angle in [0; pi] of this unit quaternion. #[inline] pub fn angle(&self) -> N { @@ -312,6 +344,75 @@ impl UnitQuaternionBase where S2: Storage { other / self } + + /// Linear interpolation between two unit quaternions. + /// + /// The result is not normalized. + #[inline] + pub fn lerp(&self, other: &UnitQuaternionBase, t: N) -> OwnedQuaternionBase + where S2: Storage { + self.as_ref().lerp(other.as_ref(), t) + } + + /// Normalized linear interpolation between two unit quaternions. + #[inline] + pub fn nlerp(&self, other: &UnitQuaternionBase, t: N) -> OwnedUnitQuaternionBase + where S2: Storage { + let mut res = self.lerp(other, t); + let _ = res.normalize_mut(); + + UnitQuaternionBase::new_unchecked(res) + } + + /// Spherical linear interpolation between two unit quaternions. + /// + /// Panics if the angle between both quaternion is 180 degrees (in which case the interpolation + /// is not well-defined). + #[inline] + pub fn slerp(&self, other: &UnitQuaternionBase, t: N) -> OwnedUnitQuaternionBase + where S2: Storage { + self.try_slerp(other, t, N::zero()).expect( + "Unable to perform a spherical quaternion interpolation when they \ + are 180 degree apart (the result is not unique).") + } + + /// Computes the spherical linear interpolation between two unit quaternions or returns `None` + /// if both quaternions are approximately 180 degrees apart (in which case the interpolation is + /// not well-defined). + /// + /// # Arguments + /// * `self`: the first quaternion to interpolate from. + /// * `other`: the second quaternion to interpolate toward. + /// * `t`: the interpolation parameter. Should be between 0 and 1. + /// * `epsilon`: the value bellow which the sinus of the angle separating both quaternion + /// must be to return `None`. + #[inline] + pub fn try_slerp(&self, other: &UnitQuaternionBase, t: N, epsilon: N) + -> Option> + where S2: Storage { + + let c_hang = self.coords.dot(&other.coords); + + // self == other + if c_hang.abs() >= N::one() { + return Some(self.clone_owned()) + } + + let hang = c_hang.acos(); + let s_hang = (N::one() - c_hang * c_hang).sqrt(); + + // FIXME: what if s_hang is 0.0 ? The result is not well-defined. + if relative_eq!(s_hang, N::zero(), epsilon = epsilon) { + None + } + else { + let ta = ((N::one() - t) * hang).sin() / s_hang; + let tb = (t * hang).sin() / s_hang; + let res = self.as_ref() * ta + other.as_ref() * tb; + + Some(UnitQuaternionBase::new_unchecked(res)) + } + } } impl UnitQuaternionBase @@ -453,3 +554,34 @@ impl fmt::Display for UnitQuaternionBase } } } + +impl ApproxEq for UnitQuaternionBase + where N: Real + ApproxEq, + S: Storage { + type Epsilon = N; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.as_ref().relative_eq(other.as_ref(), epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) + } +} diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index c83372be..2a627d93 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -33,7 +33,7 @@ impl QuaternionBase /// Creates a new quaternion from its individual components. Note that the arguments order does /// **not** follow the storage order. /// - /// The storage order is [ x, y, z, w ]. + /// The storage order is `[ x, y, z, w ]`. #[inline] pub fn new(w: N, x: N, y: N, z: N) -> Self { let v = ColumnVector::::new(x, y, z, w); @@ -169,19 +169,44 @@ impl UnitQuaternionBase pub fn from_rotation_matrix(rotmat: &RotationBase) -> Self where SB: Storage, SB::Alloc: Allocator { - let angle = rotmat.angle(); - if let Some(axis) = rotmat.axis() { - Self::from_axis_angle(&axis, angle) + // Robust matrix to quaternion transformation. + // See http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion + let tr = rotmat[(0, 0)] + rotmat[(1, 1)] + rotmat[(2, 2)]; + let res; + + let _0_25: N = ::convert(0.25); + + if tr > N::zero() { + let denom = (tr + N::one()).sqrt() * ::convert(2.0); + res = QuaternionBase::new(_0_25 * denom, + (rotmat[(2, 1)] - rotmat[(1, 2)]) / denom, + (rotmat[(0, 2)] - rotmat[(2, 0)]) / denom, + (rotmat[(1, 0)] - rotmat[(0, 1)]) / denom); } - else if angle > ::convert(1.0f64) { - // The angle is 3.14. - -Self::identity() + else if rotmat[(0, 0)] > rotmat[(1, 1)] && rotmat[(0, 0)] > rotmat[(2, 2)] { + let denom = (N::one() + rotmat[(0, 0)] - rotmat[(1, 1)] - rotmat[(2, 2)]).sqrt() * ::convert(2.0); + res = QuaternionBase::new((rotmat[(2, 1)] - rotmat[(1, 2)]) / denom, + _0_25 * denom, + (rotmat[(0, 1)] + rotmat[(1, 0)]) / denom, + (rotmat[(0, 2)] + rotmat[(2, 0)]) / denom); + } + else if rotmat[(1, 1)] > rotmat[(2, 2)] { + let denom = (N::one() + rotmat[(1, 1)] - rotmat[(0, 0)] - rotmat[(2, 2)]).sqrt() * ::convert(2.0); + res = QuaternionBase::new((rotmat[(0, 2)] - rotmat[(2, 0)]) / denom, + (rotmat[(0, 1)] + rotmat[(1, 0)]) / denom, + _0_25 * denom, + (rotmat[(1, 2)] + rotmat[(2, 1)]) / denom); } else { - // The angle is 0. - Self::identity() + let denom = (N::one() + rotmat[(2, 2)] - rotmat[(0, 0)] - rotmat[(1, 1)]).sqrt() * ::convert(2.0); + res = QuaternionBase::new((rotmat[(1, 0)] - rotmat[(0, 1)]) / denom, + (rotmat[(0, 2)] + rotmat[(2, 0)]) / denom, + (rotmat[(1, 2)] + rotmat[(2, 1)]) / denom, + _0_25 * denom); } + + Self::new_unchecked(res) } /// The unit quaternion needed to make `a` and `b` be collinear and point toward the same diff --git a/src/geometry/quaternion_coordinates.rs b/src/geometry/quaternion_coordinates.rs index aeb5beca..fdf4169c 100644 --- a/src/geometry/quaternion_coordinates.rs +++ b/src/geometry/quaternion_coordinates.rs @@ -1,5 +1,5 @@ -use std::ops::{Deref, DerefMut}; use std::mem; +use std::ops::{Deref, DerefMut}; use alga::general::Real; diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index 651ade5d..d447d585 100644 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -15,8 +15,8 @@ pub type OwnedRotation = RotationBase>:: /// A rotation matrix. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] -pub struct RotationBase> { +#[derive(Hash, Debug, Clone, Copy, Serialize, Deserialize)] +pub struct RotationBase { matrix: SquareMatrix } diff --git a/src/geometry/rotation_alga.rs b/src/geometry/rotation_alga.rs index e53c53f3..de226095 100644 --- a/src/geometry/rotation_alga.rs +++ b/src/geometry/rotation_alga.rs @@ -201,20 +201,23 @@ impl Rotation> for RotationBase, SB::Alloc: OwnedAllocator { #[inline] - fn powf(&self, n: N) -> Option { - // XXX: add the general case. + fn powf(&self, _: N) -> Option { + // XXX: Add the general case. + // XXX: Use specialization for 2D and 3D. unimplemented!() } #[inline] - fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option { - // XXX: add the general case. + fn rotation_between(_: &ColumnVector, _: &ColumnVector) -> Option { + // XXX: Add the general case. + // XXX: Use specialization for 2D and 3D. unimplemented!() } #[inline] - fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, n: N) -> Option { - // XXX: add the general case. + fn scaled_rotation_between(_: &ColumnVector, _: &ColumnVector, _: N) -> Option { + // XXX: Add the general case. + // XXX: Use specialization for 2D and 3D. unimplemented!() } } diff --git a/src/geometry/rotation_ops.rs b/src/geometry/rotation_ops.rs index c3643149..ca9b907b 100644 --- a/src/geometry/rotation_ops.rs +++ b/src/geometry/rotation_ops.rs @@ -118,7 +118,7 @@ md_impl_all!( ); -// RotationBase *= RotationBase +// RotationBase ×= RotationBase // FIXME: try not to call `inverse()` explicitly. md_assign_impl_all!( diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index 1a1190c4..9c0f13bb 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -44,7 +44,7 @@ where N: Real, pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Self where SB: Storage, SC: Storage { - UnitComplex::rotation_between(a, b).to_rotation_matrix() + ::convert(UnitComplex::rotation_between(a, b).to_rotation_matrix()) } /// The smallest rotation needed to make `a` and `b` collinear and point toward the same @@ -53,7 +53,7 @@ where N: Real, pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Self where SB: Storage, SC: Storage { - UnitComplex::scaled_rotation_between(a, b, s).to_rotation_matrix() + ::convert(UnitComplex::scaled_rotation_between(a, b, s).to_rotation_matrix()) } } @@ -81,6 +81,8 @@ where N: Real, other * self.inverse() } + /// Raise the quaternion to a given floating power, i.e., returns the rotation with the angle + /// of `self` multiplied by `n`. #[inline] pub fn powf(&self, n: N) -> OwnedRotation { OwnedRotation::<_, _, S::Alloc>::new(self.angle() * n) @@ -338,6 +340,8 @@ where N: Real, other * self.inverse() } + /// Raise the quaternion to a given floating power, i.e., returns the rotation with the same + /// axis as `self` and an angle equal to `self.angle()` multiplied by `n`. #[inline] pub fn powf(&self, n: N) -> OwnedRotation { if let Some(axis) = self.axis() { diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index 7b4d76f8..2a072bd6 100644 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -10,10 +10,16 @@ use core::storage::{Storage, OwnedStorage}; use core::allocator::{Allocator, OwnedAllocator}; use geometry::{PointBase, TranslationBase, IsometryBase}; + +/// A similarity that uses a data storage deduced from the allocator `A`. +pub type OwnedSimilarityBase = + SimilarityBase>::Buffer, R>; + /// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] +#[derive(Hash, Debug, Clone, Copy, Serialize, Deserialize)] pub struct SimilarityBase { + /// The part of this similarity that does not include the scaling factor. pub isometry: IsometryBase, scaling: N } @@ -62,12 +68,6 @@ impl SimilarityBase self.isometry.translation.vector *= self.scaling; } - /// The scaling factor of this similarity transformation. - #[inline] - pub fn scaling(&self) -> N { - self.scaling - } - /// The scaling factor of this similarity transformation. #[inline] pub fn set_scaling(&mut self, scaling: N) { @@ -162,6 +162,12 @@ impl SimilarityBase res } + + /// The scaling factor of this similarity transformation. + #[inline] + pub fn scaling(&self) -> N { + self.scaling + } } diff --git a/src/geometry/similarity_alias.rs b/src/geometry/similarity_alias.rs index 870d3a88..3bf96e75 100644 --- a/src/geometry/similarity_alias.rs +++ b/src/geometry/similarity_alias.rs @@ -12,7 +12,7 @@ pub type Similarity2 = SimilarityBase, UnitComp /// A 3-dimensional similarity. pub type Similarity3 = SimilarityBase, UnitQuaternion>; -/// A 3-dimensional similarity using a rotation matrix for its rotation part. +/// A 2-dimensional similarity using a rotation matrix for its rotation part. pub type SimilarityMatrix2 = Similarity; /// A 3-dimensional similarity using a rotation matrix for its rotation part. diff --git a/src/geometry/similarity_construction.rs b/src/geometry/similarity_construction.rs index 443de37a..d8f039f1 100644 --- a/src/geometry/similarity_construction.rs +++ b/src/geometry/similarity_construction.rs @@ -12,7 +12,8 @@ use core::dimension::{DimName, U1, U2, U3, U4}; use core::allocator::{OwnedAllocator, Allocator}; use core::storage::OwnedStorage; -use geometry::{PointBase, TranslationBase, RotationBase, SimilarityBase, UnitQuaternionBase, IsometryBase}; +use geometry::{PointBase, TranslationBase, RotationBase, SimilarityBase, + UnitComplex, UnitQuaternionBase, IsometryBase}; impl SimilarityBase @@ -106,6 +107,17 @@ impl SimilarityBase> } } +impl SimilarityBase> + where N: Real, + S: OwnedStorage, + S::Alloc: OwnedAllocator { + /// Creates a new similarity from a translation and a rotation angle. + #[inline] + pub fn new(translation: ColumnVector, angle: N, scaling: N) -> Self { + Self::from_parts(TranslationBase::from_vector(translation), UnitComplex::new(angle), scaling) + } +} + // 3D rotation. macro_rules! similarity_construction_impl( ($Rot: ty, $RotId: ident, $RRDim: ty, $RCDim: ty) => { diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index 831f6cab..726e0342 100644 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -14,6 +14,8 @@ use core::allocator::Allocator; /// /// NOTE: this trait is not intended to be implementable outside of the `nalgebra` crate. pub trait TCategory: Any + Debug + Copy + PartialEq + Send { + /// Indicates whether a `Transform` with the category `Self` has a bottom-row different from + /// `0 0 .. 1`. #[inline] fn has_normalizer() -> bool { true @@ -28,16 +30,19 @@ pub trait TCategory: Any + Debug + Copy + PartialEq + Send { N::Epsilon: Copy; } -/// Traits that gives the transformation category that is compatible with the result of the +/// Traits that gives the `Transform` category that is compatible with the result of the /// multiplication of transformations with categories `Self` and `Other`. pub trait TCategoryMul: TCategory { + /// The transform category that results from the multiplication of a `Transform` to a + /// `Transform`. This is usually equal to `Self` or `Other`, whichever is the most + /// general category. type Representative: TCategory; } -/// Indicates that `Self` is a more general transformation category than `Other`. +/// Indicates that `Self` is a more general `Transform` category than `Other`. pub trait SuperTCategoryOf: TCategory { } -/// Indicates that `Self` is a more specific transformation category than `Other`. +/// Indicates that `Self` is a more specific `Transform` category than `Other`. /// /// Automatically implemented based on `SuperTCategoryOf`. pub trait SubTCategoryOf: TCategory { } @@ -46,17 +51,17 @@ where T1: TCategory, T2: SuperTCategoryOf { } -/// Tag representing the most general (not necessarily inversible) transformation type. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct TGeneral; +/// Tag representing the most general (not necessarily inversible) `Transform` type. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum TGeneral { } -/// Tag representing the most general inversible transformation type. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct TProjective; +/// Tag representing the most general inversible `Transform` type. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum TProjective { } -/// Tag representing an affine transformation. Its bottom-row is equal to `(0, 0 ... 0, 1)`. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct TAffine; +/// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum TAffine { } impl TCategory for TGeneral { #[inline] @@ -150,9 +155,11 @@ pub type OwnedTransform /// It is stored as a matrix with dimensions `(D + 1, D + 1)`, e.g., it stores a 4x4 matrix for a /// 3D transformation. #[repr(C)] -#[derive(Debug, Clone, Copy)] // FIXME: Hash +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] // FIXME: Hash pub struct TransformBase, S, C: TCategory> { matrix: SquareMatrix, S>, + + #[serde(skip_serializing, skip_deserializing)] _phantom: PhantomData } diff --git a/src/geometry/transform_alias.rs b/src/geometry/transform_alias.rs index 1919916c..c387fb4f 100644 --- a/src/geometry/transform_alias.rs +++ b/src/geometry/transform_alias.rs @@ -3,14 +3,27 @@ use core::dimension::{U1, U2, U3, DimNameSum}; use geometry::{TransformBase, TGeneral, TProjective, TAffine}; -pub type Transform = TransformBase, DimNameSum>, TGeneral>; +/// A `D`-dimensional general transformation that may not be inversible. Stored as an homogeneous +/// `(D + 1) × (D + 1)` matrix. +pub type Transform = TransformBase, DimNameSum>, TGeneral>; + +/// An inversible `D`-dimensional general transformation. Stored as an homogeneous +/// `(D + 1) × (D + 1)` matrix. pub type Projective = TransformBase, DimNameSum>, TProjective>; -pub type Affine = TransformBase, DimNameSum>, TAffine>; -pub type Transform2 = Transform; +/// A `D`-dimensional affine transformation. Stored as an homogeneous `(D + 1) × (D + 1)` matrix. +pub type Affine = TransformBase, DimNameSum>, TAffine>; + +/// A 2D general transformation that may not be inversible. Stored as an homogeneous 3x3 matrix. +pub type Transform2 = Transform; +/// An inversible 2D general transformation. Stored as an homogeneous 3x3 matrix. pub type Projective2 = Projective; -pub type Affine2 = Affine; +/// A 2D affine transformation. Stored as an homogeneous 3x3 matrix. +pub type Affine2 = Affine; -pub type Transform3 = Transform; +/// A 3D general transformation that may not be inversible. Stored as an homogeneous 4x4 matrix. +pub type Transform3 = Transform; +/// An inversible 3D general transformation. Stored as an homogeneous 4x4 matrix. pub type Projective3 = Projective; -pub type Affine3 = Affine; +/// A 3D affine transformation. Stored as an homogeneous 4x4 matrix. +pub type Affine3 = Affine; diff --git a/src/geometry/transform_ops.rs b/src/geometry/transform_ops.rs index 21a8cf81..bacc883e 100644 --- a/src/geometry/transform_ops.rs +++ b/src/geometry/transform_ops.rs @@ -13,6 +13,68 @@ use geometry::{PointBase, OwnedPoint, TransformBase, OwnedTransform, TCategory, SubTCategoryOf, SuperTCategoryOf, TGeneral, TProjective, TAffine, RotationBase, UnitQuaternionBase, IsometryBase, SimilarityBase, TranslationBase}; +/* + * + * In the following, we provide: + * ========================= + * + * Index<(usize, usize)> + * IndexMut<(usize, usize)> (where TCategory == TGeneral) + * + * (Operators) + * + * TransformBase × IsometryBase + * TransformBase × RotationBase + * TransformBase × SimilarityBase + * TransformBase × TransformBase + * TransformBase × UnitQuaternion + * FIXME: TransformBase × UnitComplex + * TransformBase × TranslationBase + * TransformBase × ColumnVector + * TransformBase × PointBase + * + * IsometryBase × TransformBase + * RotationBase × TransformBase + * SimilarityBase × TransformBase + * TranslationBase × TransformBase + * UnitQuaternionBase × TransformBase + * FIXME: UnitComplex × TransformBase + * + * FIXME: TransformBase ÷ IsometryBase + * TransformBase ÷ RotationBase + * FIXME: TransformBase ÷ SimilarityBase + * TransformBase ÷ TransformBase + * TransformBase ÷ UnitQuaternion + * TransformBase ÷ TranslationBase + * + * FIXME: IsometryBase ÷ TransformBase + * RotationBase ÷ TransformBase + * FIXME: SimilarityBase ÷ TransformBase + * TranslationBase ÷ TransformBase + * UnitQuaternionBase ÷ TransformBase + * FIXME: UnitComplex ÷ TransformBase + * + * + * (Assignment Operators) + * + * + * TransformBase ×= TransformBase + * TransformBase ×= SimilarityBase + * TransformBase ×= IsometryBase + * TransformBase ×= RotationBase + * TransformBase ×= UnitQuaternionBase + * FIXME: TransformBase ×= UnitComplex + * TransformBase ×= TranslationBase + * + * TransformBase ÷= TransformBase + * FIXME: TransformBase ÷= SimilarityBase + * FIXME: TransformBase ÷= IsometryBase + * TransformBase ÷= RotationBase + * TransformBase ÷= UnitQuaternionBase + * FIXME: TransformBase ÷= UnitComplex + * + */ + /* * * Indexing. @@ -41,61 +103,6 @@ impl IndexMut<(usize, usize)> for TransformBase } } -/* - * - * In the following, we provide: - * ========================= - * - * - * (Operators) - * - * [OK] TransformBase × IsometryBase - * [OK] TransformBase × RotationBase - * [OK] TransformBase × SimilarityBase - * [OK] TransformBase × TransformBase - * [OK] TransformBase × UnitQuaternion - * [OK] TransformBase × TranslationBase - * [OK] TransformBase × ColumnVector - * [OK] TransformBase × PointBase - * - * [OK] IsometryBase × TransformBase - * [OK] RotationBase × TransformBase - * [OK] SimilarityBase × TransformBase - * [OK] TranslationBase × TransformBase - * [OK] UnitQuaternionBase × TransformBase - * - * TransformBase ÷ IsometryBase - * [OK] TransformBase ÷ RotationBase - * TransformBase ÷ SimilarityBase - * [OK] TransformBase ÷ TransformBase - * [OK] TransformBase ÷ UnitQuaternion - * [OK] TransformBase ÷ TranslationBase - * - * IsometryBase ÷ TransformBase - * [OK] RotationBase ÷ TransformBase - * SimilarityBase ÷ TransformBase - * [OK] TranslationBase ÷ TransformBase - * [OK] UnitQuaternionBase ÷ TransformBase - * - * - * (Assignment Operators) - * - * - * [OK] TransformBase ×= TransformBase - * [OK] TransformBase ×= SimilarityBase - * [OK] TransformBase ×= IsometryBase - * [OK] TransformBase ×= RotationBase - * [OK] TransformBase ×= UnitQuaternionBase - * [OK] TransformBase ×= TranslationBase - * - * [OK] TransformBase ÷= TransformBase - * TransformBase ÷= SimilarityBase - * TransformBase ÷= IsometryBase - * [OK] TransformBase ÷= RotationBase - * [OK] TransformBase ÷= UnitQuaternionBase - * - */ - // TransformBase × ColumnVector md_impl_all!( diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 182ca2b0..2fb28671 100644 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -14,8 +14,10 @@ pub type OwnedTranslation = TranslationBase*/> { + /// The translation coordinates, i.e., how much is added to a point's coordinates when it is + /// translated. pub vector: ColumnVector } diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 29d07ab6..0d4eb876 100644 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -1,12 +1,11 @@ use std::fmt; +use approx::ApproxEq; use num_complex::Complex; use alga::general::Real; -use core::{Unit, SquareMatrix, Vector1}; -use core::dimension::{U2, U3}; -use core::allocator::{OwnedAllocator, Allocator}; -use core::storage::OwnedStorage; -use geometry::{RotationBase, OwnedRotation}; +use core::{Unit, SquareMatrix, Vector1, Matrix3}; +use core::dimension::U2; +use geometry::Rotation2; /// A complex number with a norm equal to 1. pub type UnitComplex = Unit>; @@ -15,7 +14,7 @@ impl UnitComplex { /// The rotation angle in `]-pi; pi]` of this unit complex number. #[inline] pub fn angle(&self) -> N { - self.complex().im.atan2(self.complex().re) + self.im.atan2(self.re) } /// The rotation angle returned as a 1-dimensional vector. @@ -35,7 +34,7 @@ impl UnitComplex { /// Compute the conjugate of this unit complex number. #[inline] pub fn conjugate(&self) -> Self { - UnitComplex::new_unchecked(self.as_ref().conj()) + UnitComplex::new_unchecked(self.conj()) } /// Inverts this complex number if it is not zero. @@ -83,13 +82,11 @@ impl UnitComplex { /// Builds the rotation matrix corresponding to this unit complex number. #[inline] - pub fn to_rotation_matrix(&self) -> RotationBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { - let r = self.complex().re; - let i = self.complex().im; + pub fn to_rotation_matrix(&self) -> Rotation2 { + let r = self.re; + let i = self.im; - RotationBase::from_matrix_unchecked( + Rotation2::from_matrix_unchecked( SquareMatrix::<_, U2, _>::new( r, -i, i, r @@ -99,12 +96,8 @@ impl UnitComplex { /// Converts this unit complex number into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> SquareMatrix - where S: OwnedStorage, - S::Alloc: OwnedAllocator + - Allocator { - let r: OwnedRotation = self.to_rotation_matrix(); - r.to_homogeneous() + pub fn to_homogeneous(&self) -> Matrix3 { + self.to_rotation_matrix().to_homogeneous() } } @@ -113,3 +106,34 @@ impl fmt::Display for UnitComplex { write!(f, "UnitComplex angle: {}", self.angle()) } } + +impl ApproxEq for UnitComplex { + type Epsilon = N; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn relative_eq(&self, other: &Self, epsilon: Self::Epsilon, max_relative: Self::Epsilon) -> bool { + self.re.relative_eq(&other.re, epsilon, max_relative) && + self.im.relative_eq(&other.im, epsilon, max_relative) + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.re.ulps_eq(&other.re, epsilon, max_ulps) && + self.im.ulps_eq(&other.im, epsilon, max_ulps) + } +} diff --git a/src/geometry/unit_complex_conversion.rs b/src/geometry/unit_complex_conversion.rs new file mode 100644 index 00000000..b5d3bdf2 --- /dev/null +++ b/src/geometry/unit_complex_conversion.rs @@ -0,0 +1,173 @@ +use num::Zero; +use num_complex::Complex; + +use alga::general::{SubsetOf, SupersetOf, Real}; +use alga::linear::Rotation as AlgaRotation; + +use core::SquareMatrix; +use core::dimension::{U1, U2, U3}; +use core::storage::OwnedStorage; +use core::allocator::{Allocator, OwnedAllocator}; +use geometry::{PointBase, UnitComplex, RotationBase, OwnedRotation, IsometryBase, + SimilarityBase, TransformBase, SuperTCategoryOf, TAffine, TranslationBase}; + +/* + * This file provides the following conversions: + * ============================================= + * + * UnitComplex -> UnitComplex + * UnitComplex -> RotationBase + * UnitComplex -> IsometryBase + * UnitComplex -> SimilarityBase + * UnitComplex -> TransformBase + * UnitComplex -> Matrix (homogeneous) + * + * NOTE: + * UnitComplex -> Complex is already provided by: Unit -> T + */ + +impl SubsetOf> for UnitComplex + where N1: Real, + N2: Real + SupersetOf { + #[inline] + fn to_superset(&self) -> UnitComplex { + UnitComplex::new_unchecked(self.as_ref().to_superset()) + } + + #[inline] + fn is_in_subset(uq: &UnitComplex) -> bool { + ::is_convertible::<_, Complex>(uq.as_ref()) + } + + #[inline] + unsafe fn from_superset_unchecked(uq: &UnitComplex) -> Self { + Self::new_unchecked(::convert_ref_unchecked(uq.as_ref())) + } +} + +impl SubsetOf> for UnitComplex + where N1: Real, + N2: Real + SupersetOf, + S: OwnedStorage, + S::Alloc: OwnedAllocator + + Allocator + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> RotationBase { + let q: UnitComplex = self.to_superset(); + q.to_rotation_matrix().to_superset() + } + + #[inline] + fn is_in_subset(rot: &RotationBase) -> bool { + ::is_convertible::<_, OwnedRotation>(rot) + } + + #[inline] + unsafe fn from_superset_unchecked(rot: &RotationBase) -> Self { + let q = UnitComplex::::from_rotation_matrix(rot); + ::convert_unchecked(q) + } +} + + +impl SubsetOf> for UnitComplex + where N1: Real, + N2: Real + SupersetOf, + S: OwnedStorage, + R: AlgaRotation> + SupersetOf>, + S::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> IsometryBase { + IsometryBase::from_parts(TranslationBase::identity(), ::convert_ref(self)) + } + + #[inline] + fn is_in_subset(iso: &IsometryBase) -> bool { + iso.translation.vector.is_zero() + } + + #[inline] + unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + ::convert_ref_unchecked(&iso.rotation) + } +} + + +impl SubsetOf> for UnitComplex + where N1: Real, + N2: Real + SupersetOf, + S: OwnedStorage, + R: AlgaRotation> + SupersetOf>, + S::Alloc: OwnedAllocator { + #[inline] + fn to_superset(&self) -> SimilarityBase { + SimilarityBase::from_isometry(::convert_ref(self), N2::one()) + } + + #[inline] + fn is_in_subset(sim: &SimilarityBase) -> bool { + sim.isometry.translation.vector.is_zero() && + sim.scaling() == N2::one() + } + + #[inline] + unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + ::convert_ref_unchecked(&sim.isometry) + } +} + + +impl SubsetOf> for UnitComplex + where N1: Real, + N2: Real + SupersetOf, + S: OwnedStorage, + C: SuperTCategoryOf, + S::Alloc: OwnedAllocator + + Allocator + + Allocator + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> TransformBase { + TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + } + + #[inline] + fn is_in_subset(t: &TransformBase) -> bool { + >::is_in_subset(t.matrix()) + } + + #[inline] + unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + Self::from_superset_unchecked(t.matrix()) + } +} + + +impl SubsetOf> for UnitComplex + where N1: Real, + N2: Real + SupersetOf, + S: OwnedStorage, + S::Alloc: OwnedAllocator + + Allocator + + Allocator + + Allocator + + Allocator { + #[inline] + fn to_superset(&self) -> SquareMatrix { + self.to_homogeneous().to_superset() + } + + #[inline] + fn is_in_subset(m: &SquareMatrix) -> bool { + ::is_convertible::<_, OwnedRotation>(m) + } + + #[inline] + unsafe fn from_superset_unchecked(m: &SquareMatrix) -> Self { + let rot: OwnedRotation = ::convert_ref_unchecked(m); + Self::from_rotation_matrix(&rot) + } +} diff --git a/src/geometry/unit_complex_ops.rs b/src/geometry/unit_complex_ops.rs index db09fe0f..f7331903 100644 --- a/src/geometry/unit_complex_ops.rs +++ b/src/geometry/unit_complex_ops.rs @@ -3,8 +3,13 @@ use std::ops::{Mul, MulAssign, Div, DivAssign}; use alga::general::Real; use core::{Unit, ColumnVector, OwnedColumnVector}; use core::dimension::{U1, U2}; -use core::storage::Storage; -use geometry::{UnitComplex, RotationBase, PointBase, OwnedPoint}; +use core::storage::{Storage, OwnedStorage}; +use core::allocator::OwnedAllocator; +use geometry::{UnitComplex, RotationBase, + PointBase, OwnedPoint, + IsometryBase, OwnedIsometryBase, + SimilarityBase, OwnedSimilarityBase, + TranslationBase}; /* * This file provides: @@ -23,8 +28,11 @@ use geometry::{UnitComplex, RotationBase, PointBase, OwnedPoint}; * UnitComplex × ColumnVector * UnitComplex × Unit * - * NOTE: -UnitComplex is already provided by `Unit`. + * UnitComplex × IsometryBase + * UnitComplex × SimilarityBase + * UnitComplex × TranslationBase -> IsometryBase * + * NOTE: -UnitComplex is already provided by `Unit`. * * (Assignment Operators) * @@ -34,8 +42,8 @@ use geometry::{UnitComplex, RotationBase, PointBase, OwnedPoint}; * UnitComplex ÷= UnitComplex * UnitComplex ÷= RotationBase * - * FIXME: RotationBase ×= UnitComplex - * FIXME: RotationBase ÷= UnitComplex + * RotationBase ×= UnitComplex + * RotationBase ÷= UnitComplex * */ @@ -232,7 +240,7 @@ complex_op_impl_all!( [ref ref] => { let i = self.as_ref().im; let r = self.as_ref().re; - OwnedColumnVector::<_, U2, S::Alloc>::new(r * rhs[0] - i * rhs[0], i * rhs[1] + r * rhs[1]) + OwnedColumnVector::<_, U2, S::Alloc>::new(r * rhs[0] - i * rhs[1], i * rhs[0] + r * rhs[1]) }; ); @@ -247,6 +255,45 @@ complex_op_impl_all!( [ref ref] => Unit::new_unchecked(self * rhs.as_ref()); ); +// UnitComplex × IsometryBase +complex_op_impl_all!( + Mul, mul; + (U2, U1); + self: UnitComplex, rhs: IsometryBase>, + Output = OwnedIsometryBase>; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => { + let shift = self * &rhs.translation.vector; + IsometryBase::from_parts(TranslationBase::from_vector(shift), self * &rhs.rotation) + }; +); + +// UnitComplex × SimilarityBase +complex_op_impl_all!( + Mul, mul; + (U2, U1); + self: UnitComplex, rhs: SimilarityBase>, + Output = OwnedSimilarityBase>; + [val val] => &self * &rhs; + [ref val] => self * &rhs; + [val ref] => &self * rhs; + [ref ref] => SimilarityBase::from_isometry(self * &rhs.isometry, rhs.scaling()); +); + +// UnitComplex × TranslationBase +complex_op_impl_all!( + Mul, mul; + (U2, U1); + self: UnitComplex, rhs: TranslationBase, + Output = OwnedIsometryBase>; + [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * rhs.vector), self); + [ref val] => IsometryBase::from_parts(TranslationBase::from_vector( self * rhs.vector), self.clone()); + [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &rhs.vector), self); + [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector( self * &rhs.vector), self.clone()); +); + // UnitComplex ×= UnitComplex impl MulAssign> for UnitComplex { #[inline] @@ -307,3 +354,42 @@ impl<'b, N: Real, S: Storage> DivAssign<&'b RotationBase> f *self = &*self / rhs } } + + +// RotationBase ×= UnitComplex +impl MulAssign> for RotationBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn mul_assign(&mut self, rhs: UnitComplex) { + self.mul_assign(rhs.to_rotation_matrix()) + } +} + +impl<'b, N: Real, S: Storage> MulAssign<&'b UnitComplex> for RotationBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn mul_assign(&mut self, rhs: &'b UnitComplex) { + self.mul_assign(rhs.to_rotation_matrix()) + } +} + +// RotationBase ÷= UnitComplex +impl DivAssign> for RotationBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn div_assign(&mut self, rhs: UnitComplex) { + self.div_assign(rhs.to_rotation_matrix()) + } +} + +impl<'b, N: Real, S: Storage> DivAssign<&'b UnitComplex> for RotationBase + where S: OwnedStorage, + S::Alloc: OwnedAllocator { + #[inline] + fn div_assign(&mut self, rhs: &'b UnitComplex) { + self.div_assign(rhs.to_rotation_matrix()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9d269e25..f713768d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,97 @@ +/*! +# nalgebra + +**nalgebra** is a linear algebra library written for Rust targeting: + +* General-purpose linear algebra (still lacks a lot of features…) +* Real time computer graphics. +* Real time computer physics. + +## Using **nalgebra** +You will need the last stable build of the [rust compiler](http://www.rust-lang.org) +and the official package manager: [cargo](https://github.com/rust-lang/cargo). + +Simply add the following to your `Cargo.toml` file: + +```.ignore +[dependencies] +nalgebra = "0.11" +``` + + +Most useful functionalities of **nalgebra** are grouped in the root module `nalgebra::`. + +However, the recommended way to use **nalgebra** is to import types and traits +explicitly, and call free-functions using the `na::` prefix: + +```.rust +#[macro_use] +extern crate approx; // For the macro relative_eq! +extern crate nalgebra as na; +use na::{Vector3, Rotation3}; + +fn main() { + let axis = Vector3::x_axis(); + let angle = 1.57; + let b = Rotation3::from_axis_angle(&axis, angle); + + relative_eq!(b.axis().unwrap(), axis); + relative_eq!(b.angle(), angle); +} +``` + + +## Features +**nalgebra** is meant to be a general-purpose, low-dimensional, linear algebra library, with +an optimized set of tools for computer graphics and physics. Those features include: + +* A single parametrizable type `Matrix` for vectors, (square or rectangular) matrices, and slices + with dimensions known either at compile-time (using type-level integers) or at runtime. +* Matrices and vectors with compile-time sizes are statically allocated while dynamic ones are + allocated on the heap. +* Convenient aliases for low-dimensional matrices and vectors: `Vector1` to `Vector6` and + `Matrix1x1` to `Matrix6x6` (including rectangular matrices like `Matrix2x5`. +* Points sizes known at compile time, and convenience aliases: `Point1` to `Point6`. +* Translation (seen as a transformation that composes by multiplication): `Translation2`, + `Translation3`. +* Rotation matrices: `Rotation2`, `Rotation3`. +* Quaternions: `Quaternion`, `UnitQuaternion` (for 3D rotation). +* Unit complex numbers can be used for 2D rotation: `UnitComplex`. +* Algebraic entities with a norm equal to one: `Unit`, e.g., `Unit>`. +* Isometries (translation ⨯ rotation): `Isometry2`, `Isometry3` +* Similarity transformations (translation ⨯ rotation ⨯ uniform scale): `Similarity2`, `Similarity3`. +* Affine transformations stored as an homogeneous matrix: `Affine2`, `Affine3`. +* Projective (i.e. invertible) transformations stored as an homogeneous matrix: `Projective2`, + `Projective3`. +* General transformations that does not have to be invertible, stored as an homogeneous matrix: + `Transform2`, `Transform3`. +* 3D projections for computer graphics: `Perspective3`, `Orthographic3`. +* Linear algebra and data analysis operators: QR decomposition, eigen-decomposition. +* Implements all meaningful traits from the [alga](https://crates.io/crates/alga) crate for + generic programming. +*/ + + + + + // #![feature(plugin)] // // #![plugin(clippy)] -// #![allow(too_many_arguments)] -// #![allow(derive_hash_xor_eq)] -// #![allow(len_without_is_empty)] -// #![allow(transmute_ptr_to_ref)] + +#![deny(non_camel_case_types)] +#![deny(unused_parens)] +#![deny(non_upper_case_globals)] +#![deny(unused_qualifications)] +#![deny(unused_results)] +#![warn(missing_docs)] +#![doc(html_root_url = "http://nalgebra.org/doc")] #[cfg(feature = "arbitrary")] extern crate quickcheck; -extern crate rustc_serialize; +extern crate serde; +#[macro_use] +extern crate serde_derive; extern crate num_traits as num; extern crate num_complex; extern crate rand; @@ -32,11 +115,14 @@ pub use traits::*; use std::cmp::{self, PartialOrd, Ordering}; use num::Signed; -use alga::general::{Id, Identity, SupersetOf, MeetSemilattice, JoinSemilattice, Lattice, Inverse, +use alga::general::{Identity, SupersetOf, MeetSemilattice, JoinSemilattice, Lattice, Inverse, Multiplicative, Additive, AdditiveGroup}; use alga::linear::SquareMatrix as AlgaSquareMatrix; use alga::linear::{InnerSpace, NormedSpace, FiniteDimVectorSpace, EuclideanSpace}; +pub use alga::general::Id; + + /* * * Multiplicative identity. @@ -279,7 +365,7 @@ pub fn try_inverse(m: &M) -> Option { m.try_inverse() } -/// Computes the the multiplicative inverse (transformation, unit quaternion, etc.) +/// Computes the multiplicative inverse of an (always invertible) algebraic entity. #[inline] pub fn inverse>(m: &M) -> M { m.inverse() @@ -295,7 +381,7 @@ pub fn dot(a: &V, b: &V) -> V::Field { a.dot(b) } -/// Computes the angle between two vectors. +/// Computes the smallest angle between two vectors. #[inline] pub fn angle(a: &V, b: &V) -> V::Real { a.angle(b) @@ -305,25 +391,25 @@ pub fn angle(a: &V, b: &V) -> V::Real { * Normed space */ -/// Computes the L2 norm of a vector. +/// Computes the L2 (euclidean) norm of a vector. #[inline] pub fn norm(v: &V) -> V::Field { v.norm() } -/// Computes the squared L2 norm of a vector. +/// Computes the squared L2 (euclidean) norm of the vector `v`. #[inline] pub fn norm_squared(v: &V) -> V::Field { v.norm_squared() } -/// Gets the normalized version of a vector. +/// Computes the normalized version of the vector `v`. #[inline] pub fn normalize(v: &V) -> V { v.normalize() } -/// Gets the normalized version of a vector or `None` if its norm is smaller than `min_norm`. +/// Computes the normalized version of the vector `v` or returns `None` if its norm is smaller than `min_norm`. #[inline] pub fn try_normalize(v: &V, min_norm: V::Field) -> Option { v.try_normalize(min_norm) diff --git a/tests/isometry.rs b/tests/isometry.rs index 7c04fac8..2a5990b3 100644 --- a/tests/isometry.rs +++ b/tests/isometry.rs @@ -10,7 +10,10 @@ extern crate alga; extern crate nalgebra as na; use alga::linear::{Transformation, ProjectiveTransformation}; -use na::{Vector3, Point3, Rotation3, Isometry3, Translation3, UnitQuaternion}; +use na::{ + Vector3, Point3, Rotation3, Isometry3, Translation3, UnitQuaternion, + Vector2, Point2, Rotation2, Isometry2, Translation2, UnitComplex +}; quickcheck!( fn append_rotation_wrt_point_to_id(r: UnitQuaternion, p: Point3) -> bool { @@ -65,8 +68,39 @@ quickcheck!( relative_eq!(i.inverse() * p, i.inverse_transform_point(&p), epsilon = 1.0e-7) } - fn composition(i: Isometry3, uq: UnitQuaternion, r: Rotation3, - t: Translation3, v: Vector3, p: Point3) -> bool { + fn composition2(i: Isometry2, uc: UnitComplex, r: Rotation2, + t: Translation2, v: Vector2, p: Point2) -> bool { + // (rotation × translation) * point = rotation × (translation * point) + relative_eq!((uc * t) * v, uc * v, epsilon = 1.0e-7) && + relative_eq!((r * t) * v, r * v, epsilon = 1.0e-7) && + relative_eq!((uc * t) * p, uc * (t * p), epsilon = 1.0e-7) && + relative_eq!((r * t) * p, r * (t * p), epsilon = 1.0e-7) && + + // (translation × rotation) * point = translation × (rotation * point) + (t * uc) * v == uc * v && + (t * r) * v == r * v && + (t * uc) * p == t * (uc * p) && + (t * r) * p == t * (r * p) && + + // (rotation × isometry) * point = rotation × (isometry * point) + relative_eq!((uc * i) * v, uc * (i * v), epsilon = 1.0e-7) && + relative_eq!((uc * i) * p, uc * (i * p), epsilon = 1.0e-7) && + + // (isometry × rotation) * point = isometry × (rotation * point) + relative_eq!((i * uc) * v, i * (uc * v), epsilon = 1.0e-7) && + relative_eq!((i * uc) * p, i * (uc * p), epsilon = 1.0e-7) && + + // (translation × isometry) * point = translation × (isometry * point) + relative_eq!((t * i) * v, (i * v), epsilon = 1.0e-7) && + relative_eq!((t * i) * p, t * (i * p), epsilon = 1.0e-7) && + + // (isometry × translation) * point = isometry × (translation * point) + relative_eq!((i * t) * v, i * v, epsilon = 1.0e-7) && + relative_eq!((i * t) * p, i * (t * p), epsilon = 1.0e-7) + } + + fn composition3(i: Isometry3, uq: UnitQuaternion, r: Rotation3, + t: Translation3, v: Vector3, p: Point3) -> bool { // (rotation × translation) * point = rotation × (translation * point) relative_eq!((uq * t) * v, uq * v, epsilon = 1.0e-7) && relative_eq!((r * t) * v, r * v, epsilon = 1.0e-7) && diff --git a/tests/matrix_slice.rs b/tests/matrix_slice.rs index 10bdc9f4..6587fecb 100644 --- a/tests/matrix_slice.rs +++ b/tests/matrix_slice.rs @@ -1,8 +1,3 @@ -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; extern crate num_traits as num; extern crate nalgebra as na; diff --git a/tests/projection.rs b/tests/projection.rs new file mode 100644 index 00000000..6e25e5a3 --- /dev/null +++ b/tests/projection.rs @@ -0,0 +1,51 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; + +extern crate nalgebra as na; + +use na::{Point3, Perspective3, Orthographic3}; + +#[test] +fn perspective_inverse() { + let proj = Perspective3::new(800.0 / 600.0, 3.14 / 2.0, 1.0, 1000.0); + let inv = proj.inverse(); + + let id = inv * proj.unwrap(); + + assert!(id.is_identity(1.0e-7)); +} + +#[test] +fn orthographic_inverse() { + let proj = Orthographic3::new(1.0, 2.0, -3.0, -2.5, 10.0, 900.0); + let inv = proj.inverse(); + + let id = inv * proj.unwrap(); + + assert!(id.is_identity(1.0e-7)); +} + + +#[cfg(feature = "arbitrary")] +quickcheck!{ + fn perspective_project_unproject(pt: Point3) -> bool { + let proj = Perspective3::new(800.0 / 600.0, 3.14 / 2.0, 1.0, 1000.0); + + let projected = proj.project_point(&pt); + let unprojected = proj.unproject_point(&projected); + + relative_eq!(pt, unprojected, epsilon = 1.0e-7) + } + + fn orthographic_project_unproject(pt: Point3) -> bool { + let proj = Orthographic3::new(1.0, 2.0, -3.0, -2.5, 10.0, 900.0); + + let projected = proj.project_point(&pt); + let unprojected = proj.unproject_point(&projected); + + relative_eq!(pt, unprojected, epsilon = 1.0e-7) + } +} diff --git a/tests/rotation.rs b/tests/rotation.rs index 2a2e0af3..d683c3a9 100644 --- a/tests/rotation.rs +++ b/tests/rotation.rs @@ -7,6 +7,7 @@ extern crate num_traits as num; extern crate alga; extern crate nalgebra as na; +use std::f64; use alga::general::Real; use na::{Vector2, Vector3, Rotation2, Rotation3, Unit}; @@ -67,7 +68,6 @@ quickcheck!( a.angle(&b) == b.angle(&a) } - /* * * RotationBase matrix between vectors. diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 00000000..f55512c5 --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,47 @@ +extern crate rand; +extern crate nalgebra as na; + +extern crate serde_json; + +use na::{ + DMatrix, + Matrix3x4, + Point3, + Translation3, + Rotation3, + Isometry3, + IsometryMatrix3, + Similarity3, + SimilarityMatrix3, + Quaternion +}; + +macro_rules! test_serde( + ($($test: ident, $ty: ident);* $(;)*) => {$( + #[test] + fn $test() { + let v: $ty = rand::random(); + let serialized = serde_json::to_string(&v).unwrap(); + assert_eq!(v, serde_json::from_str(&serialized).unwrap()); + } + )*} +); + +#[test] +fn serde_dmatrix() { + let v: DMatrix = DMatrix::new_random(3, 4); + let serialized = serde_json::to_string(&v).unwrap(); + assert_eq!(v, serde_json::from_str(&serialized).unwrap()); +} + +test_serde!( + serde_matrix3x4, Matrix3x4; + serde_point3, Point3; + serde_translation3, Translation3; + serde_rotation3, Rotation3; + serde_isometry3, Isometry3; + serde_isometry_matrix3, IsometryMatrix3; + serde_similarity3, Similarity3; + serde_similarity_matrix3, SimilarityMatrix3; + serde_quaternion, Quaternion; +); diff --git a/tests/unit_complex.rs b/tests/unit_complex.rs new file mode 100644 index 00000000..3d578276 --- /dev/null +++ b/tests/unit_complex.rs @@ -0,0 +1,171 @@ +#![allow(non_snake_case)] + +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use na::{Unit, UnitComplex, Vector2, Point2, Rotation2}; + + +quickcheck!( + + /* + * + * From/to rotation matrix. + * + */ + fn unit_complex_rotation_conversion(c: UnitComplex) -> bool { + let r = c.to_rotation_matrix(); + let cc = UnitComplex::from_rotation_matrix(&r); + let rr = cc.to_rotation_matrix(); + + relative_eq!(c, cc, epsilon = 1.0e-7) && + relative_eq!(r, rr, epsilon = 1.0e-7) + } + + /* + * + * PointBase/Vector transformation. + * + */ + fn unit_complex_transformation(c: UnitComplex, v: Vector2, p: Point2) -> bool { + let r = c.to_rotation_matrix(); + let rv = r * v; + let rp = r * p; + + relative_eq!( c * v, rv, epsilon = 1.0e-7) && + relative_eq!( c * &v, rv, epsilon = 1.0e-7) && + relative_eq!(&c * v, rv, epsilon = 1.0e-7) && + relative_eq!(&c * &v, rv, epsilon = 1.0e-7) && + + relative_eq!( c * p, rp, epsilon = 1.0e-7) && + relative_eq!( c * &p, rp, epsilon = 1.0e-7) && + relative_eq!(&c * p, rp, epsilon = 1.0e-7) && + relative_eq!(&c * &p, rp, epsilon = 1.0e-7) + } + + /* + * + * Inversion. + * + */ + fn unit_complex_inv(c: UnitComplex) -> bool { + let iq = c.inverse(); + relative_eq!(&iq * &c, UnitComplex::identity(), epsilon = 1.0e-7) && + relative_eq!( iq * &c, UnitComplex::identity(), epsilon = 1.0e-7) && + relative_eq!(&iq * c, UnitComplex::identity(), epsilon = 1.0e-7) && + relative_eq!( iq * c, UnitComplex::identity(), epsilon = 1.0e-7) && + + relative_eq!(&c * &iq, UnitComplex::identity(), epsilon = 1.0e-7) && + relative_eq!( c * &iq, UnitComplex::identity(), epsilon = 1.0e-7) && + relative_eq!(&c * iq, UnitComplex::identity(), epsilon = 1.0e-7) && + relative_eq!( c * iq, UnitComplex::identity(), epsilon = 1.0e-7) + } + + /* + * + * Quaterion * Vector == RotationBase * Vector + * + */ + fn unit_complex_mul_vector(c: UnitComplex, v: Vector2, p: Point2) -> bool { + let r = c.to_rotation_matrix(); + + relative_eq!(c * v, r * v, epsilon = 1.0e-7) && + relative_eq!(c * p, r * p, epsilon = 1.0e-7) + } + + // Test that all operators (incl. all combinations of references) work. + // See the top comment on `geometry/quaternion_ops.rs` for details on which operations are + // supported. + fn all_op_exist(uc: UnitComplex, v: Vector2, p: Point2, r: Rotation2) -> bool { + let uv = Unit::new_normalize(v); + + let ucMuc = uc * uc; + let ucMr = uc * r; + let rMuc = r * uc; + let ucDuc = uc / uc; + let ucDr = uc / r; + let rDuc = r / uc; + + let ucMp = uc * p; + let ucMv = uc * v; + let ucMuv = uc * uv; + + let mut ucMuc1 = uc; + let mut ucMuc2 = uc; + + let mut ucMr1 = uc; + let mut ucMr2 = uc; + + let mut ucDuc1 = uc; + let mut ucDuc2 = uc; + + let mut ucDr1 = uc; + let mut ucDr2 = uc; + + ucMuc1 *= uc; + ucMuc2 *= &uc; + + ucMr1 *= r; + ucMr2 *= &r; + + ucDuc1 /= uc; + ucDuc2 /= &uc; + + ucDr1 /= r; + ucDr2 /= &r; + + ucMuc1 == ucMuc && + ucMuc1 == ucMuc2 && + + ucMr1 == ucMr && + ucMr1 == ucMr2 && + + ucDuc1 == ucDuc && + ucDuc1 == ucDuc2 && + + ucDr1 == ucDr && + ucDr1 == ucDr2 && + + ucMuc == &uc * &uc && + ucMuc == uc * &uc && + ucMuc == &uc * uc && + + ucMr == &uc * &r && + ucMr == uc * &r && + ucMr == &uc * r && + + rMuc == &r * &uc && + rMuc == r * &uc && + rMuc == &r * uc && + + ucDuc == &uc / &uc && + ucDuc == uc / &uc && + ucDuc == &uc / uc && + + ucDr == &uc / &r && + ucDr == uc / &r && + ucDr == &uc / r && + + rDuc == &r / &uc && + rDuc == r / &uc && + rDuc == &r / uc && + + ucMp == &uc * &p && + ucMp == uc * &p && + ucMp == &uc * p && + + ucMv == &uc * &v && + ucMv == uc * &v && + ucMv == &uc * v && + + ucMuv == &uc * &uv && + ucMuv == uc * &uv && + ucMuv == &uc * uv + } +); From 14e9194f6967ef9a86ecfedf227e237580beb268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 14 Feb 2017 18:49:25 +0100 Subject: [PATCH 4/8] Update the changelog. --- CHANGELOG.md | 97 +++++++++++++++++++++++++++++++++++ Makefile | 2 +- README.md | 15 +++++- examples/scalar_genericity.rs | 1 - 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f8e34fb..c5db34ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,103 @@ documented here. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.11.0] +The [website](http://nalgebra.org) has been fully rewritten and gives a good +overview of all the added/modified features. + +This version is a major rewrite of the library. Major changes are: + * Algebraic traits are now defined by the [alga](https://crates.io/crates/alga) crate. + * Methods are now preferred to free functions because they do not require any + trait to be used any more. + * Most algebraic entities can be parametrized by type-level integers + to specify their dimensions. Using `Dynamic` instead of a type-level + integer indicates that the dimension known at run-time only. + * Statically-sized **rectangular** matrices. + * More transformation types have been added: unit-sized complex numbers (for + 2D rotations), affine/projective/general transformations with `Affine2/3`, + `Projective2/3`, and `Transform2/3`. + * Serde serialization is now supported instead of `rustc_serialize`. + * Matrix **slices** are now implemented. + +### Added +Lots of features including rectangular matrices, slices, and Serde +serialization. Refer to the brand new [website](http://nalgebra.org) for more +details. The following free-functions have been added as well: + * `::id()` that returns the universal [identity element](http://nalgebra.org/performance_tricks/#the-id-type) + of type `Id`. + * `::inf_sup()` that returns both the infimum and supremum of a value at the + same time. + * `::partial_sort2()` that attempts to sort two values in increasing order. + * `::wrap()` that moves a value to the given interval by adding or removing + the interval width to it. + +### Modified + * `::cast` -> `::convert` + * `point.as_vector()` -> `point.coords` + * `na::origin` -> `P::origin()` + * `na::is_zero` -> `.is_zero()` (from num::Zero) + * `.transform` -> `.transform_point`/`.transform_vector` + * `.translate` -> `.translate_point` + * `::dimension::

` -> `::dimension::` + * `::angle_between` -> `::angle` + +The following free-functions are now replaced by methods (with the same names) +only: +`::cross`, `::cholesky`, `::determinant`, `::diagonal`, `::eigen_qr` (becomes +`.eig`), `::hessenberg`, `::qr`, `::to_homogeneous`, `::to_rotation_matrix`, +`::transpose`, `::shape`. + + +The following free-functions are now replaced by static methods only: + * `::householder_matrix` under the name `::new_householder_generic` + * `::identity` + * `::new_identity` under the name `::identity` + * `::from_homogeneous` + * `::repeat` under the name `::from_element` + +The following free-function are now replaced methods accessible through traits +only: + * `::transform` -> methods `.transform_point` and `.transform_vector` of the `alga::linear::Transformation` trait. + * `::inverse_transform` -> methods `.inverse_transform_point` and + `.inverse_transform_vector` of the `alga::linear::ProjectiveTransformation` + trait. + * `::translate`, `::inverse_translate`, `::rotate`, `::inverse_rotate` -> + methods from the `alga::linear::Similarity` trait instead. Those have the + same names but end with `_point` or `_vector`, e.g., `.translate_point` and + `.translate_vector`. + * `::orthonormal_subspace_basis` -> method with the same name from + `alga::linear::FiniteDimInnerSpace`. + * `::canonical_basis_element` and `::canonical_basis` -> methods with the + same names from `alga::linear::FiniteDimVectorSpace`. + * `::rotation_between` -> method with the same name from the + `alga::linear::Rotation` trait. + * `::is_zero` -> method with the same name from `num::Zero`. + + + +### Removed + * The free functions `::prepend_rotation`, `::append_rotation`, + `::append_rotation_wrt_center`, `::append_rotation_wrt_point`, + `::append_transformation`, and `::append_translation ` have been removed. + Instead create the rotation or translation object explicitly and use + multiplication to compose it with anything else. + + * The free function `::outer` has been removed. Use column-vector × + row-vector multiplication instead. + + * `::approx_eq`, `::approx_eq_eps` have been removed. Use the `relative_eq!` + macro from the [approx](https://crates.io/crates/approx) crate instead. + + * `::covariance` has been removed. There is no replacement for now. + * `::mean` has been removed. There is no replacement for now. + * `::sample_sphere` has been removed. There is no replacement for now. + * `::cross_matrix` has been removed. There is no replacement for now. + * `::absolute_rotate` has been removed. There is no replacement for now. + * `::rotation`, `::transformation`, `::translation`, `::inverse_rotation`, + `::inverse_transformation`, `::inverse_translation` have been removed. Use + the appropriate methods/field of each transformation type, e.g., + `rotation.angle()` and `rotation.axis()`. + ## [0.10.0] ### Added Binary operations are now allowed between references as well. For example diff --git a/Makefile b/Makefile index 00457c11..c2b68b5c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all: CARGO_INCREMENTAL=1 cargo build --features "arbitrary" doc: - CARGO_INCREMENTAL=1 cargo doc + CARGO_INCREMENTAL=1 cargo doc --no-deps bench: cargo bench diff --git a/README.md b/README.md index 7d6a99fa..bf337cd5 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,14 @@ crates.io

+ + Build status + crates.io - - Build status + +

@@ -14,3 +17,11 @@ Users guide | Documentation | Forum

+ +----- + +
+Linear algebra library +
+… for the [Rust](https://www.rust-lang.org) programming language. +
diff --git a/examples/scalar_genericity.rs b/examples/scalar_genericity.rs index 156d93ab..70a25d14 100644 --- a/examples/scalar_genericity.rs +++ b/examples/scalar_genericity.rs @@ -11,7 +11,6 @@ fn print_vector(m: &Vector3) { fn print_squared_norm(v: &Vector3) { // NOTE: alternatively, nalgebra already defines `v.squared_norm()`. let sqnorm = v.dot(v); - println!("{:?}", sqnorm); } From 896ad19dd020004af499abe34902676188063476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 14 Feb 2017 18:54:47 +0100 Subject: [PATCH 5/8] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bf337cd5..1cb98bef 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,7 @@ ----- -
-Linear algebra library -
-… for the [Rust](https://www.rust-lang.org) programming language. -
+

+Linear algebra library +for the Rust programming language. +

From 42b48563be24a0f7ede7e91f85fa41c4a5292765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 15 Feb 2017 22:04:34 +0100 Subject: [PATCH 6/8] Make serde optional behind the "serde-serialize" feature. --- .travis.yml | 4 +- CHANGELOG.md | 5 +- Cargo.toml | 7 ++- Makefile | 6 +- README.md | 2 +- src/core/coordinates.rs | 3 +- src/core/dimension.rs | 9 ++- src/core/matrix.rs | 5 +- src/core/matrix_array.rs | 15 ++++- src/core/matrix_vec.rs | 3 +- src/core/mod.rs | 2 +- src/core/unit.rs | 3 +- src/geometry/isometry.rs | 5 +- src/geometry/mod.rs | 3 +- src/geometry/orthographic.rs | 3 +- src/geometry/perspective.rs | 3 +- src/geometry/point.rs | 3 +- src/geometry/quaternion.rs | 118 ++++++++++++++++++++++++++++++++++- src/geometry/rotation.rs | 3 +- src/geometry/similarity.rs | 3 +- src/geometry/transform.rs | 14 +++-- src/geometry/translation.rs | 3 +- src/geometry/unit_complex.rs | 94 ++++++++++++++++++++++++++++ src/lib.rs | 4 +- 24 files changed, 284 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2b1fd312..e19edab4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,7 @@ language: rust script: - rustc --version - cargo --version + - cargo build --verbose - cargo build --verbose --features arbitrary - - cargo test --verbose --features arbitrary + - cargo build --verbose --features serde-serialize + - cargo test --verbose --features arbitrary serde-serialize diff --git a/CHANGELOG.md b/CHANGELOG.md index c5db34ad..2644b8be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ overview of all the added/modified features. This version is a major rewrite of the library. Major changes are: * Algebraic traits are now defined by the [alga](https://crates.io/crates/alga) crate. + All other mathematical traits, except `Axpy` have been removed from + **nalgebra**. * Methods are now preferred to free functions because they do not require any trait to be used any more. * Most algebraic entities can be parametrized by type-level integers @@ -19,7 +21,8 @@ This version is a major rewrite of the library. Major changes are: * More transformation types have been added: unit-sized complex numbers (for 2D rotations), affine/projective/general transformations with `Affine2/3`, `Projective2/3`, and `Transform2/3`. - * Serde serialization is now supported instead of `rustc_serialize`. + * Serde serialization is now supported instead of `rustc_serialize`. Enable + it with the `serde-serialize` feature. * Matrix **slices** are now implemented. ### Added diff --git a/Cargo.toml b/Cargo.toml index 398f0db9..d109fe3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ path = "src/lib.rs" [features] arbitrary = [ "quickcheck" ] +serde-serialize = [ "serde", "serde_derive" ] [dependencies] typenum = "1.4" @@ -25,9 +26,9 @@ rand = "0.3" num-traits = "0.1" num-complex = "0.1" approx = "0.1" -alga = "0.4" -serde = "0.9" -serde_derive = "0.9" +alga = "0.5" +serde = { version = "0.9", optional = true } +serde_derive = { version = "0.9", optional = true } # clippy = "*" [dependencies.quickcheck] diff --git a/Makefile b/Makefile index c2b68b5c..ab97c6f9 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ all: - CARGO_INCREMENTAL=1 cargo build --features "arbitrary" + CARGO_INCREMENTAL=1 cargo build --features "arbitrary serde-serialize" doc: - CARGO_INCREMENTAL=1 cargo doc --no-deps + CARGO_INCREMENTAL=1 cargo doc --no-deps --features "arbitrary serde-serialize" bench: cargo bench test: - CARGO_INCREMENTAL=1 cargo test --features "arbitrary" + CARGO_INCREMENTAL=1 cargo test --features "arbitrary serde-serialize" diff --git a/README.md b/README.md index 1cb98bef..c7eeeabb 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

- Users guide | Documentation | Forum + Users guide | Documentation | Forum

diff --git a/src/core/coordinates.rs b/src/core/coordinates.rs index 3a8f371f..63d81166 100644 --- a/src/core/coordinates.rs +++ b/src/core/coordinates.rs @@ -23,7 +23,8 @@ macro_rules! coords_impl( /// Data structure used to provide access to matrix and vector coordinates with the dot /// notation, e.g., `v.x` is the same as `v[0]` for a vector. #[repr(C)] - #[derive(Eq, PartialEq, Clone, Hash, Debug, Copy, Serialize, Deserialize)] + #[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)] + #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct $T { $(pub $comps: N),* } diff --git a/src/core/dimension.rs b/src/core/dimension.rs index 20819b68..d3bd819d 100644 --- a/src/core/dimension.rs +++ b/src/core/dimension.rs @@ -8,7 +8,8 @@ use std::ops::{Add, Sub, Mul, Div}; use typenum::{self, Unsigned, UInt, B1, Bit, UTerm, Sum, Prod, Diff, Quot}; /// Dim of dynamically-sized algebraic entities. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct Dynamic { value: usize } @@ -161,7 +162,8 @@ pub trait NamedDim: Sized + Any + Unsigned { type Name: DimName; } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct U1; impl Dim for U1 { @@ -197,7 +199,8 @@ impl NamedDim for typenum::U1{ macro_rules! named_dimension( ($($D: ident),* $(,)*) => {$( - #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct $D; impl Dim for $D { diff --git a/src/core/matrix.rs b/src/core/matrix.rs index c92b76a8..004888e8 100644 --- a/src/core/matrix.rs +++ b/src/core/matrix.rs @@ -79,13 +79,14 @@ Matrix>::Alloc as Allocator>: /// dynamically-sized column vector should be represented as a `Matrix` (given /// some concrete types for `N` and a compatible data storage type `S`). #[repr(C)] -#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy)] +#[derive(Hash, Debug, Clone, Copy)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct Matrix { /// The data storage that contains all the matrix components and informations about its number /// of rows and column (if needed). pub data: S, - #[serde(skip_serializing, skip_deserializing)] + #[cfg_attr(feature = "serde-serialize", serde(skip_serializing, skip_deserializing))] _phantoms: PhantomData<(N, R, C)> } diff --git a/src/core/matrix_array.rs b/src/core/matrix_array.rs index 5ef36198..ebc20e00 100644 --- a/src/core/matrix_array.rs +++ b/src/core/matrix_array.rs @@ -1,11 +1,17 @@ -use std::mem; -use std::marker::PhantomData; use std::ops::{Deref, DerefMut, Mul}; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; + +#[cfg(feature = "serde-serialize")] use serde::{Serialize, Serializer, Deserialize, Deserializer}; +#[cfg(feature = "serde-serialize")] use serde::ser::SerializeSeq; +#[cfg(feature = "serde-serialize")] use serde::de::{SeqVisitor, Visitor}; +#[cfg(feature = "serde-serialize")] +use std::mem; +#[cfg(feature = "serde-serialize")] +use std::marker::PhantomData; use typenum::Prod; use generic_array::{ArrayLength, GenericArray}; @@ -199,6 +205,7 @@ unsafe impl OwnedStorage for MatrixArray * */ // XXX: open an issue for GenericArray so that it implements serde traits? +#[cfg(feature = "serde-serialize")] impl Serialize for MatrixArray where N: Scalar + Serialize, R: DimName, @@ -220,6 +227,7 @@ where N: Scalar + Serialize, } +#[cfg(feature = "serde-serialize")] impl Deserialize for MatrixArray where N: Scalar + Deserialize, R: DimName, @@ -237,11 +245,13 @@ where N: Scalar + Deserialize, } +#[cfg(feature = "serde-serialize")] /// A visitor that produces a matrix array. struct MatrixArrayVisitor { marker: PhantomData<(N, R, C)> } +#[cfg(feature = "serde-serialize")] impl MatrixArrayVisitor where N: Scalar, R: DimName, @@ -257,6 +267,7 @@ where N: Scalar, } } +#[cfg(feature = "serde-serialize")] impl Visitor for MatrixArrayVisitor where N: Scalar + Deserialize, R: DimName, diff --git a/src/core/matrix_vec.rs b/src/core/matrix_vec.rs index b492ec47..51a02640 100644 --- a/src/core/matrix_vec.rs +++ b/src/core/matrix_vec.rs @@ -12,7 +12,8 @@ use core::default_allocator::DefaultAllocator; */ /// A Vec-based matrix data storage. It may be dynamically-sized. #[repr(C)] -#[derive(Eq, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct MatrixVec { data: Vec, nrows: R, diff --git a/src/core/mod.rs b/src/core/mod.rs index f37ec9b8..356dd29d 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,4 @@ -//! Data structures for vector and matrix computations. +//! [Reexported at the root of this crate.] Data structures for vector and matrix computations. pub mod dimension; pub mod constraint; diff --git a/src/core/unit.rs b/src/core/unit.rs index c8e8c578..cc7ea1df 100644 --- a/src/core/unit.rs +++ b/src/core/unit.rs @@ -10,7 +10,8 @@ use alga::linear::NormedSpace; /// /// Use `.as_ref()` or `.unwrap()` to obtain the undelying value by-reference or by-move. #[repr(C)] -#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct Unit { value: T } diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 5be41990..67d2e7e8 100644 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -18,7 +18,8 @@ pub type OwnedIsometryBase = /// A direct isometry, i.e., a rotation followed by a translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Hash, Debug, Clone, Copy)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct IsometryBase { /// The pure rotational part of this isometry. pub rotation: R, @@ -27,7 +28,7 @@ pub struct IsometryBase { // One dummy private field just to prevent explicit construction. - #[serde(skip_serializing, skip_deserializing)] + #[cfg_attr(feature = "serde-serialize", serde(skip_serializing, skip_deserializing))] _noconstruct: PhantomData } diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index 0ee4c9e0..d99e82cb 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -1,4 +1,5 @@ -//! Data structures for points and usual transformations (rotations, isometries, etc.) +//! [Reexported at the root of this crate.] Data structures for points and usual transformations +//! (rotations, isometries, etc.) mod op_macros; diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index 900fd74a..3936ccef 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -13,7 +13,8 @@ use core::helper; use geometry::{PointBase, OwnedPoint}; /// A 3D orthographic projection stored as an homogeneous 4x4 matrix. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] // FIXME: Hash +#[derive(Debug, Clone, Copy)] // FIXME: Hash +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct OrthographicBase> { matrix: SquareMatrix } diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index c9110ea9..9c518546 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -13,7 +13,8 @@ use core::helper; use geometry::{PointBase, OwnedPoint}; /// A 3D perspective projection stored as an homogeneous 4x4 matrix. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] // FIXME: Hash +#[derive(Debug, Clone, Copy)] // FIXME: Hash +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct PerspectiveBase> { matrix: SquareMatrix } diff --git a/src/geometry/point.rs b/src/geometry/point.rs index 70cba65f..370fc3db 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -23,7 +23,8 @@ pub type OwnedPoint = PointBase>::Buffe /// A point in a n-dimensional euclidean space. #[repr(C)] -#[derive(Hash, Debug, Serialize, Deserialize)] +#[derive(Hash, Debug)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct PointBase> { /// The coordinates of this point, i.e., the shift from the origin. pub coords: ColumnVector diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index e970ae15..784dc20a 100644 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -21,7 +21,8 @@ pub type OwnedUnitQuaternionBase = UnitQuaternionBase` for a quaternion /// that may be used as a rotation. #[repr(C)] -#[derive(Hash, Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Hash, Debug, Copy, Clone)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct QuaternionBase> { /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. pub coords: ColumnVector @@ -275,6 +276,121 @@ impl fmt::Display for QuaternionBase } /// A unit quaternions. May be used to represent a rotation. +/// +/// +///
+/// +/// Due to a [bug](https://github.com/rust-lang/rust/issues/32077) in rustdoc, the documentation +/// below has been written manually lists only method signatures.
+/// Trait implementations are not listed either. +///
+///
+/// +/// Please refer directly to the documentation written above each function definition on the source +/// code for more details. +/// +///

Methods

+/// +/// +///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

+///

pub type UnitQuaternionBase = Unit>; diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index d447d585..a1063cbb 100644 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -15,7 +15,8 @@ pub type OwnedRotation = RotationBase>:: /// A rotation matrix. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Hash, Debug, Clone, Copy)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct RotationBase { matrix: SquareMatrix } diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index 2a072bd6..43e4ec1f 100644 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -17,7 +17,8 @@ pub type OwnedSimilarityBase = /// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Hash, Debug, Clone, Copy)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct SimilarityBase { /// The part of this similarity that does not include the scaling factor. pub isometry: IsometryBase, diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index 726e0342..23563d80 100644 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -52,15 +52,18 @@ where T1: TCategory, } /// Tag representing the most general (not necessarily inversible) `Transform` type. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub enum TGeneral { } /// Tag representing the most general inversible `Transform` type. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub enum TProjective { } /// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub enum TAffine { } impl TCategory for TGeneral { @@ -155,11 +158,12 @@ pub type OwnedTransform /// It is stored as a matrix with dimensions `(D + 1, D + 1)`, e.g., it stores a 4x4 matrix for a /// 3D transformation. #[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] // FIXME: Hash +#[derive(Debug, Clone, Copy)] // FIXME: Hash +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct TransformBase, S, C: TCategory> { matrix: SquareMatrix, S>, - #[serde(skip_serializing, skip_deserializing)] + #[cfg_attr(feature = "serde-serialize", serde(skip_serializing, skip_deserializing))] _phantom: PhantomData } diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 2fb28671..ac7b1fd8 100644 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -14,7 +14,8 @@ pub type OwnedTranslation = TranslationBase*/> { /// The translation coordinates, i.e., how much is added to a point's coordinates when it is /// translated. diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 0d4eb876..8d371234 100644 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -8,6 +8,100 @@ use core::dimension::U2; use geometry::Rotation2; /// A complex number with a norm equal to 1. +/// +///
+/// +/// Due to a [bug](https://github.com/rust-lang/rust/issues/32077) in rustdoc, the documentation +/// below has been written manually lists only method signatures.
+/// Trait implementations are not listed either. +///
+///
+/// +/// Please refer directly to the documentation written above each function definition on the source +/// code for more details. +/// +/// +///

Methods

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

+/// +///

pub type UnitComplex = Unit>; impl UnitComplex { diff --git a/src/lib.rs b/src/lib.rs index f713768d..76a551e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ an optimized set of tools for computer graphics and physics. Those features incl `Transform2`, `Transform3`. * 3D projections for computer graphics: `Perspective3`, `Orthographic3`. * Linear algebra and data analysis operators: QR decomposition, eigen-decomposition. -* Implements all meaningful traits from the [alga](https://crates.io/crates/alga) crate for +* Implements traits from the [alga](https://crates.io/crates/alga) crate for generic programming. */ @@ -89,7 +89,9 @@ an optimized set of tools for computer graphics and physics. Those features incl #[cfg(feature = "arbitrary")] extern crate quickcheck; +#[cfg(feature = "serde")] extern crate serde; +#[cfg(feature = "serde")] #[macro_use] extern crate serde_derive; extern crate num_traits as num; From a18fd7343f66b0ac28be36cd648a1d59f190ab61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 15 Feb 2017 22:16:15 +0100 Subject: [PATCH 7/8] Fix travis.yml. --- .travis.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e19edab4..57a75a7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,20 @@ +sudo: false language: rust - +matrix: + include: + - rust: nightly + env: TEST_SUITE=suite_nightly + - rust: beta + env: TEST_SUITE=suite_beta + - rust: stable + env: TEST_SUITE=suite_stable + allow_failures: + - rust: nightly + - rust: beta script: - rustc --version - cargo --version - cargo build --verbose - cargo build --verbose --features arbitrary - cargo build --verbose --features serde-serialize - - cargo test --verbose --features arbitrary serde-serialize + - cargo test --verbose --features "arbitrary serde-serialize" From 181e67604dbf6667cc4bc50a81172529b50a5d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 15 Feb 2017 22:30:57 +0100 Subject: [PATCH 8/8] Release v0.11.0. Fix #211, #207, #205, #200, #145, #136, #108, #50, #32 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d109fe3a..5f98c1a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.10.1" +version = "0.11.0" authors = [ "Sébastien Crozet " ] description = "Linear algebra library for computer physics, computer graphics and general low-dimensional linear algebra for Rust."

- */ - -/// Applies a translation to a point. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Point3, Vector3, Isometry3}; -/// -/// fn main() { -/// let t = Isometry3::new(Vector3::new(1.0f64, 1.0, 1.0), na::zero()); -/// let p = Point3::new(2.0, 2.0, 2.0); -/// -/// let tp = na::translate(&t, &p); -/// -/// assert!(tp == Point3::new(3.0, 3.0, 3.0)) -/// } -/// ``` -#[inline] -pub fn translate>(m: &M, p: &P) -> P { - m.translate(p) -} - -/// Applies an inverse translation to a point. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Point3, Vector3, Isometry3}; -/// -/// fn main() { -/// let t = Isometry3::new(Vector3::new(1.0f64, 1.0, 1.0), na::zero()); -/// let p = Point3::new(2.0, 2.0, 2.0); -/// -/// let tp = na::inverse_translate(&t, &p); -/// -/// assert!(na::approx_eq(&tp, &Point3::new(1.0, 1.0, 1.0))) -/// } -#[inline] -pub fn inverse_translate>(m: &M, p: &P) -> P { - m.inverse_translate(p) -} - -/* - * Rotation - */ - -/// Gets the rotation applicable by `m`. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Vector3, Rotation3}; -/// -/// fn main() { -/// let t = Rotation3::new(Vector3::new(1.0f64, 1.0, 1.0)); -/// -/// assert!(na::approx_eq(&na::rotation(&t), &Vector3::new(1.0, 1.0, 1.0))); -/// } -/// ``` -#[inline] -pub fn rotation>(m: &M) -> V { - m.rotation() -} - - -/// Gets the inverse rotation applicable by `m`. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Vector3, Rotation3}; -/// -/// fn main() { -/// let t = Rotation3::new(Vector3::new(1.0f64, 1.0, 1.0)); -/// -/// assert!(na::approx_eq(&na::inverse_rotation(&t), &Vector3::new(-1.0, -1.0, -1.0))); -/// } -/// ``` -#[inline] -pub fn inverse_rotation>(m: &M) -> V { - m.inverse_rotation() -} - -// FIXME: this example is a bit shity -/// Applies the rotation `v` to a copy of `m`. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Vector3, Rotation3}; -/// -/// fn main() { -/// let t = Rotation3::new(Vector3::new(0.0f64, 0.0, 0.0)); -/// let v = Vector3::new(1.0, 1.0, 1.0); -/// let rt = na::append_rotation(&t, &v); -/// -/// assert!(na::approx_eq(&na::rotation(&rt), &Vector3::new(1.0, 1.0, 1.0))) -/// } -/// ``` -#[inline] -pub fn append_rotation>(m: &M, v: &V) -> M { - Rotation::append_rotation(m, v) -} - -// FIXME: this example is a bit shity -/// Pre-applies the rotation `v` to a copy of `m`. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{Vector3, Rotation3}; -/// -/// fn main() { -/// let t = Rotation3::new(Vector3::new(0.0f64, 0.0, 0.0)); -/// let v = Vector3::new(1.0, 1.0, 1.0); -/// let rt = na::prepend_rotation(&t, &v); -/// -/// assert!(na::approx_eq(&na::rotation(&rt), &Vector3::new(1.0, 1.0, 1.0))) -/// } -/// ``` -#[inline] -pub fn prepend_rotation>(m: &M, v: &V) -> M { - Rotation::prepend_rotation(m, v) -} - -/* - * Rotate - */ - -/// Applies a rotation to a vector. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{BaseFloat, Rotation3, Vector3}; -/// -/// fn main() { -/// let t = Rotation3::new(Vector3::new(0.0f64, 0.0, 0.5 * ::pi())); -/// let v = Vector3::new(1.0, 0.0, 0.0); -/// -/// let tv = na::rotate(&t, &v); -/// -/// assert!(na::approx_eq(&tv, &Vector3::new(0.0, 1.0, 0.0))) -/// } -/// ``` -#[inline] -pub fn rotate>(m: &M, v: &V) -> V { - m.rotate(v) -} - - -/// Applies an inverse rotation to a vector. -/// -/// ```rust -/// extern crate nalgebra as na; -/// use na::{BaseFloat, Rotation3, Vector3}; -/// -/// fn main() { -/// let t = Rotation3::new(Vector3::new(0.0f64, 0.0, 0.5 * ::pi())); -/// let v = Vector3::new(1.0, 0.0, 0.0); -/// -/// let tv = na::inverse_rotate(&t, &v); -/// -/// assert!(na::approx_eq(&tv, &Vector3::new(0.0, -1.0, 0.0))) -/// } -/// ``` -#[inline] -pub fn inverse_rotate>(m: &M, v: &V) -> V { - m.inverse_rotate(v) -} - -/* - * RotationWithTranslation - */ - -/// Rotates a copy of `m` by `amount` using `center` as the pivot point. -#[inline] -pub fn append_rotation_wrt_point + Copy, - AV, - M: RotationWithTranslation>( - m: &M, - amount: &AV, - center: &LV) -> M { - RotationWithTranslation::append_rotation_wrt_point(m, amount, center) -} - -/// Rotates a copy of `m` by `amount` using `m.translation()` as the pivot point. -#[inline] -pub fn append_rotation_wrt_center + Copy, - AV, - M: RotationWithTranslation>( - m: &M, - amount: &AV) -> M { - RotationWithTranslation::append_rotation_wrt_center(m, amount) -} - -/* - * RotationTo - */ -/// Computes the angle of the rotation needed to transfom `a` to `b`. -#[inline] -pub fn angle_between(a: &V, b: &V) -> V::AngleType { - a.angle_to(b) -} - -/// Computes the rotation needed to transform `a` to `b`. -#[inline] -pub fn rotation_between(a: &V, b: &V) -> V::DeltaRotationType { - a.rotation_to(b) -} - -/* - * RotationMatrix - */ - -/// Builds a rotation matrix from `r`. -#[inline] -pub fn to_rotation_matrix(r: &R) -> M - where R: RotationMatrix, - M: SquareMatrix + Rotation + Copy, - LV: Mul -{ - // FIXME: rust-lang/rust#20413 - r.to_rotation_matrix() -} - -/* - * AbsoluteRotate - */ - -/// Applies a rotation using the absolute values of its components. -#[inline] -pub fn absolute_rotate>(m: &M, v: &V) -> V { - m.absolute_rotate(v) -} - -/* - * Transformation - */ - -/// Gets the transformation applicable by `m`. -#[inline] -pub fn transformation>(m: &M) -> T { - m.transformation() -} - -/// Gets the inverse transformation applicable by `m`. -#[inline] -pub fn inverse_transformation>(m: &M) -> T { - m.inverse_transformation() -} - -/// Gets a transformed copy of `m`. -#[inline] -pub fn append_transformation>(m: &M, t: &T) -> M { - Transformation::append_transformation(m, t) -} - -/* - * Transform - */ - -/// Applies a transformation to a vector. -#[inline] -pub fn transform>(m: &M, v: &V) -> V { - m.transform(v) -} - -/// Applies an inverse transformation to a vector. -#[inline] -pub fn inverse_transform>(m: &M, v: &V) -> V { - m.inverse_transform(v) -} - -/* - * Dot - */ - -/// Computes the dot product of two vectors. -#[inline] -pub fn dot, N>(a: &V, b: &V) -> N { - Dot::dot(a, b) -} - -/* - * Norm - */ - -/// Computes the L2 norm of a vector. -#[inline] -pub fn norm(v: &V) -> V::NormType { - Norm::norm(v) -} - -/// Computes the squared L2 norm of a vector. -#[inline] -pub fn norm_squared(v: &V) -> V::NormType { - Norm::norm_squared(v) -} - -/// Gets the normalized version of a vector. -#[inline] -pub fn normalize(v: &V) -> V { - Norm::normalize(v) -} - -/// Gets the normalized version of a vector or `None` if its norm is smaller than `min_norm`. -#[inline] -pub fn try_normalize(v: &V, min_norm: V::NormType) -> Option { - Norm::try_normalize(v, min_norm) -} - -/* - * Determinant - */ -/// Computes the determinant of a square matrix. -#[inline] -pub fn determinant, N>(m: &M) -> N { - Determinant::determinant(m) -} - -/* - * Cross - */ - -/// Computes the cross product of two vectors. -#[inline] -pub fn cross(a: &LV, b: &LV) -> LV::CrossProductType { - Cross::cross(a, b) -} - -/* - * CrossMatrix - */ - -/// Given a vector, computes the matrix which, when multiplied by another vector, computes a cross -/// product. -#[inline] -pub fn cross_matrix, M>(v: &V) -> M { - CrossMatrix::cross_matrix(v) -} - -/* - * ToHomogeneous - */ - -/// Converts a matrix or vector to homogeneous coordinates. -#[inline] -pub fn to_homogeneous, Res>(m: &M) -> Res { - ToHomogeneous::to_homogeneous(m) -} - -/* - * FromHomogeneous - */ - -/// Converts a matrix or vector from homogeneous coordinates. -/// -/// w-normalization is appied. -#[inline] -pub fn from_homogeneous>(m: &M) -> Res { - FromHomogeneous::from(m) -} - -/* - * UniformSphereSample - */ - -/// Samples the unit sphere living on the dimension as the samples types. -/// -/// The number of sampling point is implementation-specific. It is always uniform. -#[inline] -pub fn sample_sphere(f: F) { - UniformSphereSample::sample(f) -} - -// -// -// Operations -// -// - -/* - * AproxEq - */ -/// Tests approximate equality. -#[inline] -pub fn approx_eq, N>(a: &T, b: &T) -> bool { - ApproxEq::approx_eq(a, b) -} - -/// Tests approximate equality using a custom epsilon. -#[inline] -pub fn approx_eq_eps, N>(a: &T, b: &T, eps: &N) -> bool { - ApproxEq::approx_eq_eps(a, b, eps) -} - - -/* - * Absolute - */ - -/// Computes a component-wise absolute value. -#[inline] -pub fn abs, Res>(m: &M) -> Res { - Absolute::abs(m) +pub fn partial_sort2<'a, T: PartialOrd>(a: &'a T, b: &'a T) -> Option<(&'a T, &'a T)> { + if let Some(ord) = a.partial_cmp(b) { + match ord { + Ordering::Less => Some((a, b)), + _ => Some((b, a)), + } + } + else { + None + } } /* * Inverse */ -/// Gets an inverted copy of a matrix. +/// Tries to gets an inverted copy of a square matrix. #[inline] -pub fn inverse(m: &M) -> Option { - Inverse::inverse(m) +pub fn try_inverse(m: &M) -> Option { + m.try_inverse() +} + +/// Computes the the multiplicative inverse (transformation, unit quaternion, etc.) +#[inline] +pub fn inverse>(m: &M) -> M { + m.inverse() } /* - * Transpose + * Inner vector space */ -/// Gets a transposed copy of a matrix. +/// Computes the dot product of two vectors. #[inline] -pub fn transpose(m: &M) -> M { - Transpose::transpose(m) +pub fn dot(a: &V, b: &V) -> V::Field { + a.dot(b) +} + +/// Computes the angle between two vectors. +#[inline] +pub fn angle(a: &V, b: &V) -> V::Real { + a.angle(b) } /* - * Outer + * Normed space */ -/// Computes the outer product of two vectors. +/// Computes the L2 norm of a vector. #[inline] -pub fn outer(a: &V, b: &V) -> V::OuterProductType { - Outer::outer(a, b) +pub fn norm(v: &V) -> V::Field { + v.norm() +} + +/// Computes the squared L2 norm of a vector. +#[inline] +pub fn norm_squared(v: &V) -> V::Field { + v.norm_squared() +} + +/// Gets the normalized version of a vector. +#[inline] +pub fn normalize(v: &V) -> V { + v.normalize() +} + +/// Gets the normalized version of a vector or `None` if its norm is smaller than `min_norm`. +#[inline] +pub fn try_normalize(v: &V, min_norm: V::Field) -> Option { + v.try_normalize(min_norm) } /* - * Covariance + * + * Point operations. + * */ - -/// Computes the covariance of a set of observations. +/// The center of two points. #[inline] -pub fn covariance, Res>(observations: &M) -> Res { - Covariance::covariance(observations) +pub fn center(p1: &P, p2: &P) -> P { + P::from_coordinates((p1.coordinates() + p2.coordinates()) * convert(0.5)) +} + +/// The distance between two points. +#[inline] +pub fn distance(p1: &P, p2: &P) -> P::Real { + (p2.coordinates() - p1.coordinates()).norm() +} + +/// The squared distance between two points. +#[inline] +pub fn distance_squared(p1: &P, p2: &P) -> P::Real { + (p2.coordinates() - p1.coordinates()).norm_squared() } /* - * Mean + * Cast */ - -/// Computes the mean of a set of observations. +/// Converts an object from one type to an equivalent or more general one. #[inline] -pub fn mean>(observations: &M) -> N { - Mean::mean(observations) +pub fn convert>(t: From) -> To { + To::from_subset(&t) } -/* - * EigenQR - */ -/// Computes the eigenvalues and eigenvectors of a square matrix usin the QR algorithm. +/// Attempts to convert an object to a more specific one. #[inline] -pub fn eigen_qr(m: &M, eps: &N, niter: usize) -> (M, V) - where V: Mul, - M: EigenQR { - EigenQR::eigen_qr(m, eps, niter) +pub fn try_convert, To>(t: From) -> Option { + t.to_subset() } -// -// -// Structure -// -// - -/* - * Eye - */ -/// Construct the identity matrix for a given dimension +/// Indicates if `::try_convert` will succeed without actually performing the conversion. #[inline] -pub fn new_identity(dimension: usize) -> M { - Eye::new_identity(dimension) +pub fn is_convertible, To>(t: &From) -> bool { + t.is_in_subset() } - -/* - * Repeat - */ -/// Create an object by repeating a value. -/// -/// Same as `Identity::new()`. +/// Use with care! Same as `try_convert` but without any property checks. #[inline] -pub fn repeat>(val: N) -> T { - Repeat::repeat(val) +pub unsafe fn convert_unchecked, To>(t: From) -> To { + t.to_subset_unchecked() } -/* - * Basis - */ - -/// Computes the canonical basis for a given dimension. +/// Converts an object from one type to an equivalent or more general one. #[inline] -pub fn canonical_basis bool>(f: F) { - Basis::canonical_basis(f) +pub fn convert_ref>(t: &From) -> To { + To::from_subset(t) } -/// Computes the basis of the orthonormal subspace of a given vector. +/// Attempts to convert an object to a more specific one. #[inline] -pub fn orthonormal_subspace_basis bool>(v: &V, f: F) { - Basis::orthonormal_subspace_basis(v, f) +pub fn try_convert_ref, To>(t: &From) -> Option { + t.to_subset() } -/// Gets the (0-based) i-th element of the canonical basis of V. +/// Use with care! Same as `try_convert` but without any property checks. #[inline] -pub fn canonical_basis_element(i: usize) -> Option { - Basis::canonical_basis_element(i) +pub unsafe fn convert_ref_unchecked, To>(t: &From) -> To { + t.to_subset_unchecked() } - -/* - * Row - */ - -/* - * Column - */ - -/* - * Diagonal - */ -/// Gets the diagonal of a square matrix. -#[inline] -pub fn diagonal, V>(m: &M) -> V { - m.diagonal() -} - -/* - * Dimension - */ -/// Gets the dimension an object lives in. -/// -/// Same as `Dimension::dimension::(None::)`. -#[inline] -pub fn dimension() -> usize { - Dimension::dimension(None::) -} - -/// Gets the indexable range of an object. -#[inline] -pub fn shape, I>(v: &V) -> I { - v.shape() -} - -/* - * Cast - */ -/// Converts an object from one type to another. -/// -/// For primitive types, this is the same as the `as` keywords. -/// The following properties are preserved by a cast: -/// -/// * Type-level geometric invariants cannot be broken (eg. a cast from Rotation3 to Rotation3 is -/// not possible) -/// * A cast to a type with more type-level invariants cannot be done (eg. a cast from Matrix to -/// Rotation3 is not possible) -/// * For primitive types an unbounded cast is done using the `as` keyword (this is different from -/// the standard library which makes bound-checking to ensure eg. that a i64 is not out of the -/// range of an i32 when a cast from i64 to i32 is done). -/// * A cast does not affect the dimension of an algebraic object. Note that this prevents an -/// isometric transform to be cast to a raw matrix. Use `to_homogeneous` for that special purpose. -#[inline] -pub fn cast>(t: T) -> U { - Cast::from(t) -} - -/* - * Indexable - */ diff --git a/src/linalg/decompositions.rs b/src/linalg/decompositions.rs deleted file mode 100644 index 9a6751fd..00000000 --- a/src/linalg/decompositions.rs +++ /dev/null @@ -1,351 +0,0 @@ -use traits::operations::{Transpose, ApproxEq}; -use traits::structure::{ColumnSlice, Eye, Indexable, SquareMatrix, BaseFloat, Cast}; -use traits::geometry::Norm; -use std::cmp; -use std::ops::{Mul, Add, Sub}; - -/// Get the householder matrix corresponding to a reflexion to the hyperplane -/// defined by `vector`. It can be a reflexion contained in a subspace. -/// -/// # Arguments -/// * `dimension` - the dimension of the space the resulting matrix operates in -/// * `start` - the starting dimension of the subspace of the reflexion -/// * `vector` - the vector defining the reflection. -pub fn householder_matrix(dimension: usize, start: usize, vector: V) -> M - where N: BaseFloat, - M: Eye + Indexable<(usize, usize), N>, - V: Indexable { - let mut qk : M = Eye::new_identity(dimension); - let subdim = vector.shape(); - - let stop = subdim + start; - - assert!(dimension >= stop); - - for j in start .. stop { - for i in start .. stop { - unsafe { - let vv = vector.unsafe_at(i - start) * vector.unsafe_at(j - start); - let qkij = qk.unsafe_at((i, j)); - qk.unsafe_set((i, j), qkij - vv - vv); - } - } - } - qk -} - -/// QR decomposition using Householder reflections. -/// -/// # Arguments -/// * `m` - matrix to decompose -pub fn qr(m: &M) -> (M, M) - where N: BaseFloat, - V: Indexable + Norm, - M: Copy + Eye + ColumnSlice + Transpose + Indexable<(usize, usize), N> + - Mul { - let (rows, cols) = m.shape(); - assert!(rows >= cols); - let mut q : M = Eye::new_identity(rows); - let mut r = *m; - - for ite in 0 .. cmp::min(rows - 1, cols) { - let mut v = r.column_slice(ite, ite, rows); - let alpha = - if unsafe { v.unsafe_at(ite) } >= ::zero() { - -Norm::norm(&v) - } - else { - Norm::norm(&v) - }; - unsafe { - let x = v.unsafe_at(0); - v.unsafe_set(0, x - alpha); - } - if !::is_zero(&v.normalize_mut()) { - let qk: M = householder_matrix(rows, ite, v); - r = qk * r; - q = q * Transpose::transpose(&qk); - } - } - - (q, r) -} - -/// Eigendecomposition of a square symmetric matrix using the qr algorithm -pub fn eigen_qr(m: &M, eps: &N, niter: usize) -> (M, V) - where N: BaseFloat, - V: Mul, - VS: Indexable + Norm, - M: Indexable<(usize, usize), N> + SquareMatrix + Add + - Sub + ColumnSlice + - ApproxEq + Copy { - - let (mut eigenvectors, mut eigenvalues) = hessenberg(m); - - // Allocate arrays for Givens rotation components - let mut c = Vec::::with_capacity(::dimension::() - 1); - let mut s = Vec::::with_capacity(::dimension::() - 1); - - if ::dimension::() == 1 { - return (eigenvectors, eigenvalues.diagonal()); - } - - unsafe { - c.set_len(::dimension::() - 1); - s.set_len(::dimension::() - 1); - } - - let mut iter = 0; - let mut curdim = ::dimension::() - 1; - - for _ in 0 .. ::dimension::() { - - let mut stop = false; - - while !stop && iter < niter { - - let lambda; - - unsafe { - let a = eigenvalues.unsafe_at((curdim - 1, curdim - 1)); - let b = eigenvalues.unsafe_at((curdim - 1, curdim)); - let c = eigenvalues.unsafe_at((curdim, curdim - 1)); - let d = eigenvalues.unsafe_at((curdim, curdim)); - - let trace = a + d; - let determinant = a * d - b * c; - - let constquarter: N = Cast::from(0.25f64); - let consthalf: N = Cast::from(0.5f64); - - let e = (constquarter * trace * trace - determinant).sqrt(); - - let lambda1 = consthalf * trace + e; - let lambda2 = consthalf * trace - e; - - if (lambda1 - d).abs() < (lambda2 - d).abs() { - lambda = lambda1; - } - else { - lambda = lambda2; - } - - } - - // Shift matrix - for k in 0 .. curdim + 1 { - unsafe { - let a = eigenvalues.unsafe_at((k, k)); - eigenvalues.unsafe_set((k, k), a - lambda); - } - } - - - // Givens rotation from left - for k in 0 .. curdim { - let x_i = unsafe { eigenvalues.unsafe_at((k, k)) }; - let x_j = unsafe { eigenvalues.unsafe_at((k + 1, k)) }; - - let ctmp; - let stmp; - - if x_j.abs() < *eps { - ctmp = N::one(); - stmp = N::zero(); - } - else if x_i.abs() < *eps { - ctmp = N::zero(); - stmp = -N::one(); - } - else { - let r = x_i.hypot(x_j); - ctmp = x_i / r; - stmp = -x_j / r; - } - - c[k] = ctmp; - s[k] = stmp; - - for j in k .. (curdim + 1) { - - unsafe { - let a = eigenvalues.unsafe_at((k, j)); - let b = eigenvalues.unsafe_at((k + 1, j)); - - eigenvalues.unsafe_set((k, j), ctmp * a - stmp * b); - eigenvalues.unsafe_set((k + 1, j), stmp * a + ctmp * b); - } - - } - } - - // Givens rotation from right applied to eigenvalues - for k in 0 .. curdim { - for i in 0 .. (k + 2) { - - unsafe { - let a = eigenvalues.unsafe_at((i, k)); - let b = eigenvalues.unsafe_at((i, k + 1)); - - - eigenvalues.unsafe_set((i, k), c[k] * a - s[k] * b); - eigenvalues.unsafe_set((i, k + 1), s[k] * a + c[k] * b); - } - } - - } - - - // Shift back - for k in 0 .. curdim + 1 { - unsafe { - let a = eigenvalues.unsafe_at((k, k)); - eigenvalues.unsafe_set((k, k), a + lambda); - } - } - - - // Givens rotation from right applied to eigenvectors - for k in 0 .. curdim { - for i in 0 .. ::dimension::() { - - unsafe { - let a = eigenvectors.unsafe_at((i, k)); - let b = eigenvectors.unsafe_at((i, k + 1)); - - - eigenvectors.unsafe_set((i, k), c[k] * a - s[k] * b); - eigenvectors.unsafe_set((i, k + 1), s[k] * a + c[k] * b); - } - } - } - - iter = iter + 1; - - stop = true; - - for j in 0 .. curdim { - - // Check last row - if unsafe { eigenvalues.unsafe_at((curdim, j)) }.abs() >= *eps { - stop = false; - break; - } - - // Check last column - if unsafe { eigenvalues.unsafe_at((j, curdim)) }.abs() >= *eps { - stop = false; - break; - } - } - } - - - if stop { - - if curdim > 1 { - curdim = curdim - 1; - } - else { - break; - } - - } - - } - - (eigenvectors, eigenvalues.diagonal()) -} - -/// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T -/// -/// # Arguments -/// * `m` - square symmetric positive definite matrix to decompose -pub fn cholesky(m: &M) -> Result - where N: BaseFloat, - V: Mul, - VS: Indexable + Norm, - M: Indexable<(usize, usize), N> + SquareMatrix + Add + - Sub + ColumnSlice + - ApproxEq + Copy { - - let mut out = m.transpose(); - - if !ApproxEq::approx_eq(&out, &m) { - return Err("Cholesky: Input matrix is not symmetric"); - } - - for i in 0 .. out.nrows() { - for j in 0 .. (i + 1) { - - let mut sum: N = out[(i, j)]; - - for k in 0 .. j { - sum = sum - out[(i, k)] * out[(j, k)]; - } - - if i > j { - out[(i, j)] = sum / out[(j, j)]; - } - else if sum > N::zero() { - out[(i, i)] = sum.sqrt(); - } - else { - return Err("Cholesky: Input matrix is not positive definite to machine precision"); - } - } - } - - for i in 0 .. out.nrows() { - for j in i + 1 .. out.ncols() { - out[(i, j)] = N::zero(); - } - } - - Ok(out) -} - -/// Hessenberg -/// Returns the matrix m in Hessenberg form and the corresponding similarity transformation -/// -/// # Arguments -/// * `m` - matrix to transform -/// -/// # Returns -/// * First return value `q` - Similarity matrix p such that q * h * q^T = m -/// * Second return value `h` - Matrix m in Hessenberg form -pub fn hessenberg(m: &M) -> (M, M) - where N: BaseFloat, - V: Indexable + Norm, - M: Copy + Eye + ColumnSlice + Transpose + Indexable<(usize, usize), N> + - Mul { - - let mut h = *m; - let (rows, cols) = h.shape(); - - let mut q : M = Eye::new_identity(cols); - - if cols <= 2 { - return (q, h); - } - - for ite in 0 .. (cols - 2) { - let mut v = h.column_slice(ite, ite + 1, rows); - - let alpha = Norm::norm(&v); - - unsafe { - let x = v.unsafe_at(0); - v.unsafe_set(0, x - alpha); - } - - if !::is_zero(&v.normalize_mut()) { - let p: M = householder_matrix(rows, ite + 1, v); - - q = q * p; - h = p * h * p; - } - } - - (q, h) -} diff --git a/src/linalg/mod.rs b/src/linalg/mod.rs deleted file mode 100644 index 6c4f53b6..00000000 --- a/src/linalg/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ - -pub use self::decompositions::{qr, eigen_qr, householder_matrix, cholesky, hessenberg}; - -mod decompositions; diff --git a/src/macros/assert.rs b/src/macros/assert.rs deleted file mode 100644 index c791cfc4..00000000 --- a/src/macros/assert.rs +++ /dev/null @@ -1,29 +0,0 @@ -/// Asserts approximate equality within a given tolerance of two values with the -/// `ApproxEq` trait. -#[macro_export] -macro_rules! assert_approx_eq_eps( - ($given: expr, $expected: expr, $eps: expr) => ({ - let eps = &($eps); - let (given_val, expected_val) = (&($given), &($expected)); - if !ApproxEq::approx_eq_eps(given_val, expected_val, eps) { - panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)", - *given_val, *expected_val, *eps - ) - } - }) -); - -/// Asserts approximate equality within a given tolerance of two values with the -/// `ApproxEq` trait, with tolerance specified in ULPs. -#[macro_export] -macro_rules! assert_approx_eq_ulps( - ($given: expr, $expected: expr, $ulps: expr) => ({ - let ulps = $ulps; - let (given_val, expected_val) = (&($given), &($expected)); - if !ApproxEq::approx_eq_ulps(given_val, expected_val, ulps) { - panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)", - *given_val, *expected_val, ulps - ) - } - }) -); diff --git a/src/macros/mod.rs b/src/macros/mod.rs deleted file mode 100644 index 4bb1130f..00000000 --- a/src/macros/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod assert; diff --git a/src/structs/algebra/dummy.rs b/src/structs/algebra/dummy.rs deleted file mode 100644 index 0f687775..00000000 --- a/src/structs/algebra/dummy.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![macro_use] - -macro_rules! vector_space_impl( - ($t: ident, $dimension: expr, $($compN: ident),+) => { } -); - -macro_rules! special_orthogonal_group_impl( - ($t: ident, $point: ident, $vector: ident) => { } -); - -macro_rules! euclidean_space_impl( - ($t: ident, $vector: ident) => { } -); - -macro_rules! matrix_group_approx_impl( - ($t: ident, $($compN: ident),+) => { } -); diff --git a/src/structs/algebra/matrix.rs b/src/structs/algebra/matrix.rs deleted file mode 100644 index 68fc7fe7..00000000 --- a/src/structs/algebra/matrix.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![macro_use] - -macro_rules! use_matrix_group_modules( - () => { - use algebra::cmp::ApproxEq as AlgebraApproxEq; - } -); - -macro_rules! matrix_group_approx_impl( - ($t: ident, $($compN: ident),+) => { - impl AlgebraApproxEq for $t { - type Eps = N::Eps; - - #[inline] - fn default_epsilon() -> N::Eps { - N::default_epsilon() - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N::Eps) -> bool { - $(AlgebraApproxEq::approx_eq_eps(&self.$compN, &other.$compN, &epsilon))&&+ - } - } - } -); diff --git a/src/structs/algebra/mod.rs b/src/structs/algebra/mod.rs deleted file mode 100644 index 3bd34405..00000000 --- a/src/structs/algebra/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![macro_use] - -#[cfg(not(feature="abstract_algebra"))] -mod dummy; - -#[cfg(feature="abstract_algebra")] -mod vector; -#[cfg(feature="abstract_algebra")] -mod rotation; -#[cfg(feature="abstract_algebra")] -mod point; -#[cfg(feature="abstract_algebra")] -mod matrix; diff --git a/src/structs/algebra/point.rs b/src/structs/algebra/point.rs deleted file mode 100644 index a273f022..00000000 --- a/src/structs/algebra/point.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![macro_use] - -macro_rules! use_euclidean_space_modules( - () => { - use algebra::structure::{AffineSpaceApprox, EuclideanSpaceApprox, - FieldApprox, RealApprox}; - use algebra::cmp::ApproxEq as AlgebraApproxEq; - } -); - - -macro_rules! euclidean_space_impl( - ($t: ident, $vector: ident) => { - impl AffineSpaceApprox for $t - where N: Copy + Neg + Add + - Sub + AlgebraApproxEq + FieldApprox { - type Translation = $vector; - - #[inline] - fn translate_by(&self, vector: &Self::Translation) -> Self { - *self + *vector - } - - #[inline] - fn subtract(&self, other: &Self) -> Self::Translation { - *self - *other - } - } - - impl EuclideanSpaceApprox for $t { - type Vector = $vector; - } - } -); diff --git a/src/structs/algebra/rotation.rs b/src/structs/algebra/rotation.rs deleted file mode 100644 index 786aae63..00000000 --- a/src/structs/algebra/rotation.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![macro_use] - -macro_rules! use_special_orthogonal_group_modules( - () => { - use algebra::structure::{EuclideanGroupApprox, SpecialEuclideanGroupApprox, - OrthogonalGroupApprox, SpecialOrthogonalGroupApprox, - GroupApprox, LoopApprox, MonoidApprox, - QuasigroupApprox, SemigroupApprox, - RealApprox}; - use algebra::cmp::ApproxEq as AlgebraApproxEq; - use algebra::ident::Identity; - use algebra::ops::{Recip, Multiplicative}; - } -); - -macro_rules! special_orthogonal_group_impl( - ($t: ident, $point: ident, $vector: ident) => { - /* - * Operations. - */ - impl Identity for $t { - #[inline] - fn id() -> Self { - ::one() - } - } - - impl> AlgebraApproxEq for $t { - type Eps = N; - - #[inline] - fn default_epsilon() -> N { - ::default_epsilon() - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N) -> bool { - AlgebraApproxEq::approx_eq_eps(&self.submatrix, &other.submatrix, &epsilon) - } - } - - impl Recip for $t { - type Result = $t; - - #[inline] - fn recip(mut self) -> $t { - self.inverse_mut(); - - self - } - } - - - /* - * - * Algebraic structures. - * - */ - // FIXME: in the end, we will keep only RealApprox. - impl GroupApprox for $t { } - impl LoopApprox for $t { } - impl MonoidApprox for $t { } - impl QuasigroupApprox for $t { } - impl SemigroupApprox for $t { } - - /* - * - * Matrix groups. - * - */ - impl EuclideanGroupApprox> for $t { - #[inline] - fn transform_point(&self, pt: &$point) -> $point { - *self * *pt - } - - #[inline] - fn transform_vector(&self, v: &$vector) -> $vector { - *self * *v - } - } - - impl SpecialEuclideanGroupApprox> for $t { - } - - impl OrthogonalGroupApprox> for $t { - } - - impl SpecialOrthogonalGroupApprox> for $t { - } - } -); diff --git a/src/structs/algebra/vector.rs b/src/structs/algebra/vector.rs deleted file mode 100644 index ebcf2e30..00000000 --- a/src/structs/algebra/vector.rs +++ /dev/null @@ -1,186 +0,0 @@ -#![macro_use] - -macro_rules! use_vector_space_modules( - () => { - use algebra::structure::{FieldApprox, RingCommutativeApprox, GroupAbelianApprox, - GroupApprox, LoopApprox, MonoidApprox, QuasigroupApprox, - SemigroupApprox, VectorSpaceApprox, ModuleApprox, - NormedSpaceApprox, InnerSpaceApprox, - FiniteDimVectorSpaceApprox, - Field, RingCommutative, GroupAbelian, - Group, Loop, Monoid, Quasigroup, - Semigroup, VectorSpace, Module, RealApprox}; - use algebra::cmp::ApproxEq as AlgebraApproxEq; - use algebra::ident::Identity; - use algebra::ops::Additive; - } -); - -macro_rules! vector_space_impl( - ($t: ident, $dimension: expr, $($compN: ident),+) => { - /* - * Identity & ApproxEq - */ - impl> Identity for $t { - #[inline] - fn id() -> Self { - Repeat::repeat(Identity::id()) - } - } - - impl AlgebraApproxEq for $t { - type Eps = N::Eps; - - #[inline] - fn default_epsilon() -> N::Eps { - N::default_epsilon() - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N::Eps) -> bool { - $(AlgebraApproxEq::approx_eq_eps(&self.$compN, &other.$compN, &epsilon))&&+ - } - } - - /* - * - * Approximate algebraic structures. - * - */ - product_space_inherit_structure!($t, GroupAbelianApprox); - product_space_inherit_structure!($t, GroupApprox); - product_space_inherit_structure!($t, LoopApprox); - product_space_inherit_structure!($t, MonoidApprox); - product_space_inherit_structure!($t, QuasigroupApprox); - product_space_inherit_structure!($t, SemigroupApprox); - - /* - * Module. - */ - impl ModuleApprox for $t where N: Copy + Neg + Add + - AlgebraApproxEq + RingCommutativeApprox - { } - - /* - * Vector spaces. - */ - impl VectorSpaceApprox for $t - where N: Copy + Neg + Add + - AlgebraApproxEq + FieldApprox { } - - impl FiniteDimVectorSpaceApprox for $t - where N: Copy + Zero + One + Neg + Add + - AlgebraApproxEq + FieldApprox { - #[inline] - fn dimension() -> usize { - $dimension - } - - #[inline] - fn canonical_basis])>(f: F) { - let basis = [ - $($t::$compN()),* - ]; - - f(&basis[..]) - } - - #[inline] - fn component(&self, i: usize) -> N { - self[i] - } - - #[inline] - unsafe fn component_unchecked(&self, i: usize) -> N { - self.at_fast(i) - } - } - - impl NormedSpaceApprox for $t { - #[inline] - fn norm_squared(&self) -> N { - self.inner_product(self) - } - - #[inline] - fn norm(&self) -> N { - self.norm_squared().sqrt() - } - - #[inline] - fn normalize(&self) -> Self { - *self / self.norm() - } - - #[inline] - fn normalize_mut(&mut self) -> N { - let n = self.norm(); - *self /= n; - - n - } - - #[inline] - fn try_normalize(&self, min_norm: &N) -> Option { - let n = self.norm(); - - if n <= *min_norm { - None - } - else { - Some(*self / n) - } - } - - #[inline] - fn try_normalize_mut(&mut self, min_norm: &N) -> Option { - let n = self.norm(); - - if n <= *min_norm { - None - } - else { - *self /= n; - Some(n) - } - } - } - - impl InnerSpaceApprox for $t { - #[inline] - fn inner_product(&self, other: &Self) -> N { - fold_add!($(self.$compN * other.$compN ),+) - } - } - - /* - * - * Exact algebraic structures. - * - */ - - product_space_inherit_structure!($t, GroupAbelian); - product_space_inherit_structure!($t, Group); - product_space_inherit_structure!($t, Loop); - product_space_inherit_structure!($t, Monoid); - product_space_inherit_structure!($t, Quasigroup); - product_space_inherit_structure!($t, Semigroup); - - impl VectorSpace for $t - where N: Copy + Neg + Add + AlgebraApproxEq + Field - { } - - impl Module for $t - where N: Copy + Neg + Add + AlgebraApproxEq + RingCommutative - { } - } -); - -macro_rules! product_space_inherit_structure( - ($t: ident, $marker: ident<$operator: ident>) => { - impl $marker<$operator> for $t - where N: Copy + Neg + Add + AlgebraApproxEq + - $marker<$operator> - { } - } -); diff --git a/src/structs/common_macros.rs b/src/structs/common_macros.rs deleted file mode 100644 index a3583a38..00000000 --- a/src/structs/common_macros.rs +++ /dev/null @@ -1,478 +0,0 @@ -#![macro_use] - -macro_rules! ref_binop( - // Symmetric to `std::ops::forward_ref_binop!` - (impl $imp:ident, $method:ident for $t:ident; $($compN:ident),+) => { - impl<'a, 'b, N> $imp<&'a $t> for &'b $t where &'b N: $imp<&'a N, Output = N> { - type Output = $t; - - #[inline] - fn $method(self, right: &'a $t) -> $t { - $t::new( - $( - $imp::$method(&self.$compN, &right.$compN) - ),+ - ) - } - } - - impl<'a, N> $imp<&'a $t> for $t where N: $imp<&'a N, Output = N> { - type Output = $t; - - #[inline] - fn $method(self, right: &'a $t) -> $t { - $t::new( - $( - $imp::$method(self.$compN, &right.$compN) - ),+ - ) - } - } - - impl<'a, N> $imp<$t> for &'a $t where &'a N: $imp { - type Output = $t; - - #[inline] - fn $method(self, right: $t) -> $t { - $t::new( - $( - $imp::$method(&self.$compN, right.$compN) - ),+ - ) - } - } - } -); - -macro_rules! pointwise_mul( - ($t: ident, $($compN: ident),+) => ( - impl> Mul<$t> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t::new($(self.$compN * right.$compN),+) - } - } - - impl> MulAssign<$t> for $t { - #[inline] - fn mul_assign(&mut self, right: $t) { - $( self.$compN *= right.$compN; )+ - } - } - - ref_binop!(impl Mul, mul for $t; $($compN),+); - ) -); - -macro_rules! pointwise_div( - ($t: ident, $($compN: ident),+) => ( - impl> Div<$t> for $t { - type Output = $t; - - #[inline] - fn div(self, right: $t) -> $t { - $t::new($(self.$compN / right.$compN),+) - } - } - - impl> DivAssign<$t> for $t { - #[inline] - fn div_assign(&mut self, right: $t) { - $( self.$compN /= right.$compN; )+ - } - } - - ref_binop!(impl Div, div for $t; $($compN),+); - ) -); - -macro_rules! pointwise_add( - ($t: ident, $($compN: ident),+) => ( - impl> Add<$t> for $t { - type Output = $t; - - #[inline] - fn add(self, right: $t) -> $t { - $t::new($(self.$compN + right.$compN),+) - } - } - - impl> AddAssign<$t> for $t { - #[inline] - fn add_assign(&mut self, right: $t) { - $( self.$compN += right.$compN; )+ - } - } - - ref_binop!(impl Add, add for $t; $($compN),+); - ) -); - - -macro_rules! pointwise_sub( - ($t: ident, $($compN: ident),+) => ( - impl> Sub<$t> for $t { - type Output = $t; - - #[inline] - fn sub(self, right: $t) -> $t { - $t::new($(self.$compN - right.$compN),+) - } - } - - - impl> SubAssign<$t> for $t { - #[inline] - fn sub_assign(&mut self, right: $t) { - $( self.$compN -= right.$compN; )+ - } - } - - ref_binop!(impl Sub, sub for $t; $($compN),+); - ) -); - - -macro_rules! ref_binop_scalar_exact( - (impl $imp:ident<$lhs:ident>, $method:ident for $t:ident; $($compN:ident),+) => { - impl $imp<$t<$lhs>> for $lhs { - type Output = $t<$lhs>; - - #[inline] - fn $method(self, right: $t<$lhs>) -> $t<$lhs> { - $t::new( - $( - $imp::$method(right.$compN, self) - ),+ - ) - } - } - - impl<'a, 'b> $imp<&'a $t<$lhs>> for &'b $lhs { - type Output = $t<$lhs>; - - #[inline] - fn $method(self, right: &'a $t<$lhs>) -> $t<$lhs> { - $t::new( - $( - $imp::$method(right.$compN, self) - ),+ - ) - } - } - - impl<'a> $imp<$t<$lhs>> for &'a $lhs { - type Output = $t<$lhs>; - - #[inline] - fn $method(self, right: $t<$lhs>) -> $t<$lhs> { - $t::new( - $( - $imp::$method(right.$compN, self) - ),+ - ) - } - } - - impl<'a> $imp<&'a $t<$lhs>> for $lhs { - type Output = $t<$lhs>; - - #[inline] - fn $method(self, right: &'a $t<$lhs>) -> $t<$lhs> { - $t::new( - $( - $imp::$method(right.$compN, self) - ),+ - ) - } - } - }; -); - - -macro_rules! ref_binop_scalar( - // Symmetric to `std::ops::forward_ref_binop!` - (impl $imp:ident, $method:ident for $t:ident; $($compN:ident),+) => { - ref_binop_scalar_exact!(impl $imp, $method for $t; $($compN),+); - ref_binop_scalar_exact!(impl $imp, $method for $t; $($compN),+); - - impl> $imp for $t { - type Output = $t; - - #[inline] - fn $method(self, right: N) -> $t { - $t::new( - $( - $imp::$method(self.$compN, right) - ),+ - ) - } - } - - impl<'a, 'b, N> $imp<&'a N> for &'b $t where &'b N: $imp<&'a N, Output = N> { - type Output = $t; - - #[inline] - fn $method(self, right: &'a N) -> $t { - $t::new( - $( - $imp::$method(&self.$compN, &right) - ),+ - ) - } - } - - impl<'a, N> $imp for &'a $t where for<'b> &'a N: $imp<&'b N, Output = N> { - type Output = $t; - - #[inline] - fn $method(self, right: N) -> $t { - $t::new( - $( - $imp::$method(&self.$compN, &right) - ),+ - ) - } - } - - impl<'a, N> $imp<&'a N> for $t where N: $imp<&'a N, Output = N> { - type Output = $t; - - #[inline] - fn $method(self, right: &'a N) -> $t { - $t::new( - $( - $imp::$method(self.$compN, &right) - ),+ - ) - } - } - } -); - - -macro_rules! pointwise_scalar_mul( - ($t: ident, $($compN: ident),+) => ( - impl> MulAssign for $t { - #[inline] - fn mul_assign(&mut self, right: N) { - $( self.$compN *= right; )+ - } - } - - ref_binop_scalar!(impl Mul, mul for $t; $($compN),+); - ) -); - - -macro_rules! pointwise_scalar_div( - ($t: ident, $($compN: ident),+) => ( - impl> DivAssign for $t { - #[inline] - fn div_assign(&mut self, right: N) { - $( self.$compN /= right; )+ - } - } - - ref_binop_scalar!(impl Div, div for $t; $($compN),+); - ) -); - - -macro_rules! pointwise_scalar_add( - ($t: ident, $($compN: ident),+) => ( - impl> AddAssign for $t { - #[inline] - fn add_assign(&mut self, right: N) { - $( self.$compN += right; )+ - } - } - - ref_binop_scalar!(impl Add, add for $t; $($compN),+); - ) -); - -macro_rules! pointwise_scalar_sub( - ($t: ident, $($compN: ident),+) => ( - impl> SubAssign for $t { - #[inline] - fn sub_assign(&mut self, right: N) { - $( self.$compN -= right; )+ - } - } - - ref_binop_scalar!(impl Sub, sub for $t; $($compN),+); - ) -); - - -macro_rules! ref_unop( - // Symmetric to `std::ops::forward_ref_unop!` - (impl $imp:ident, $method:ident for $t:ident; $($compN:ident),+) => { - impl + Copy> $imp for $t { - type Output = $t; - - #[inline] - fn $method(self) -> $t { - $t::new( - $( - $imp::$method(self.$compN) - ),+ - ) - } - } - - impl<'a, N> $imp for &'a $t where &'a N: $imp { - type Output = $t; - - #[inline] - fn $method(self) -> $t { - $t::new( - $( - $imp::$method(&self.$compN) - ),+ - ) - } - } - } -); - - -macro_rules! componentwise_neg( - ($t: ident, $($compN: ident),+) => ( - ref_unop!(impl Neg, neg for $t; $($compN),+); - ) -); - -macro_rules! componentwise_repeat( - ($t: ident, $($compN: ident),+) => ( - impl Repeat for $t { - fn repeat(val: N) -> $t { - $t { - $($compN: val ),+ - } - } - } - ) -); - -macro_rules! componentwise_absolute( - ($t: ident, $($compN: ident),+) => ( - impl> Absolute<$t> for $t { - #[inline] - fn abs(m: &$t) -> $t { - $t::new($(::abs(&m.$compN) ),+) - } - } - ) -); - -macro_rules! componentwise_zero( - ($t: ident, $($compN: ident),+ ) => ( - impl Zero for $t { - #[inline] - fn zero() -> $t { - $t { - $($compN: ::zero() ),+ - } - } - - #[inline] - fn is_zero(&self) -> bool { - $(::is_zero(&self.$compN) )&&+ - } - } - ) -); - -macro_rules! componentwise_one( - ($t: ident, $($compN: ident),+ ) => ( - impl One for $t { - #[inline] - fn one() -> $t { - $t { - $($compN: ::one() ),+ - } - } - } - ) -); - -// Implements Arbitrary by setting each components to Arbitrary::arbitrary. -macro_rules! componentwise_arbitrary( - ($t: ident, $($compN: ident),+ ) => ( - #[cfg(feature="arbitrary")] - impl Arbitrary for $t { - #[inline] - fn arbitrary(g: &mut G) -> $t { - $t { $($compN: Arbitrary::arbitrary(g),)* } - } - } - ) -); - -// Implements Rand by setting each components to Rand::rand. -macro_rules! componentwise_rand( - ($t: ident, $($compN: ident),+ ) => ( - impl Rand for $t { - #[inline] - fn rand(rng: &mut R) -> $t { - $t { $($compN: Rand::rand(rng), )* } - } - } - ) -); - -macro_rules! component_basis_element( - ($t: ident, $($compN: ident),+ ) => ( - /* - * - * Element of the canonical basis. - * - */ - impl $t { - $( - /// Create the element of the canonical basis having this component set to one and - /// all the others set to zero. - #[inline] - pub fn $compN() -> $t { - let mut res: $t = ::zero(); - - res.$compN = ::one(); - - res - } - )+ - } - ) -); - -// A function to create a new element from its component values. -macro_rules! component_new( - ($t: ident, $($compN: ident),+) => ( - impl $t { - /// Creation from component values. - #[inline] - pub fn new($($compN: N ),+) -> $t { - $t { - $($compN: $compN ),+ - } - } - } - ); -); - - -macro_rules! fold_add( - // base case - ($x:expr) => { - $x - }; - // `$x` followed by at least one `$y,` - ($x:expr, $($y:expr),+) => { - // call min! on the tail `$y` - Add::add($x, fold_add!($($y),+)) - } -); diff --git a/src/structs/dmatrix.rs b/src/structs/dmatrix.rs deleted file mode 100644 index 81bd9962..00000000 --- a/src/structs/dmatrix.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! Matrix with dimensions unknown at compile-time. - -use std::cmp; -use std::mem; -use std::iter::repeat; -use std::ops::{Add, Sub, Mul, Div, AddAssign, SubAssign, MulAssign, Index, IndexMut}; -use std::fmt::{Debug, Formatter, Result}; -use rand::{self, Rand}; -use num::{Zero, One}; -use structs::dvector::{DVector, DVector1, DVector2, DVector3, DVector4, DVector5, DVector6}; -use traits::operations::{ApproxEq, Inverse, Transpose, Mean, Covariance}; -use traits::structure::{Cast, Column, ColumnSlice, Row, RowSlice, Diagonal, DiagonalMut, Eye, - Indexable, Shape, BaseNum}; -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - - -/// Matrix with dimensions unknown at compile-time. -#[derive(Eq, PartialEq, Clone)] -pub struct DMatrix { - nrows: usize, - ncols: usize, - mij: Vec -} - -impl DMatrix { - /// Creates an uninitialized matrix. - #[inline] - pub unsafe fn new_uninitialized(nrows: usize, ncols: usize) -> DMatrix { - let mut vector = Vec::with_capacity(nrows * ncols); - vector.set_len(nrows * ncols); - - DMatrix { - nrows: nrows, - ncols: ncols, - mij: vector - } - } -} - -impl DMatrix { - /// Builds a matrix filled with a given constant. - #[inline] - pub fn from_element(nrows: usize, ncols: usize, val: N) -> DMatrix { - DMatrix { - nrows: nrows, - ncols: ncols, - mij: repeat(val).take(nrows * ncols).collect() - } - } - - /// Builds a matrix filled with the components provided by a vector. - /// The vector contains the matrix data in row-major order. - /// Note that `from_column_vector` is much faster than `from_row_vector` since a `DMatrix` stores its data - /// in column-major order. - /// - /// The vector must have exactly `nrows * ncols` elements. - #[inline] - pub fn from_row_vector(nrows: usize, ncols: usize, vector: &[N]) -> DMatrix { - DMatrix::from_row_iter(nrows, ncols, vector.to_vec()) - } - - /// Builds a matrix filled with the components provided by a vector. - /// The vector contains the matrix data in column-major order. - /// Note that `from_column_vector` is much faster than `from_row_vector` since a `DMatrix` stores its data - /// in column-major order. - /// - /// The vector must have exactly `nrows * ncols` elements. - #[inline] - pub fn from_column_vector(nrows: usize, ncols: usize, vector: &[N]) -> DMatrix { - DMatrix::from_column_iter(nrows, ncols, vector.to_vec()) - } - - /// Builds a matrix filled with the components provided by a source that may be moved into an iterator. - /// The source contains the matrix data in row-major order. - /// Note that `from_column_iter` is much faster than `from_row_iter` since a `DMatrix` stores its data - /// in column-major order. - /// - /// The source must have exactly `nrows * ncols` elements. - #[inline] - pub fn from_row_iter>(nrows: usize, ncols: usize, param: I) -> DMatrix { - let mut res = DMatrix::from_column_iter(ncols, nrows, param); - - // we transpose because the buffer is row_major - res.transpose_mut(); - - res - } - - - /// Builds a matrix filled with the components provided by a source that may be moved into an iterator. - /// The source contains the matrix data in column-major order. - /// Note that `from_column_iter` is much faster than `from_row_iter` since a `DMatrix` stores its data - /// in column-major order. - /// - /// The source must have exactly `nrows * ncols` elements. - #[inline] - pub fn from_column_iter>(nrows: usize, ncols: usize, param: I) -> DMatrix { - let mij: Vec = param.into_iter().collect(); - - assert!(nrows * ncols == mij.len(), "The ammount of data provided does not matches the matrix size."); - - DMatrix { - nrows: nrows, - ncols: ncols, - mij: mij - } - } -} - -impl DMatrix { - /// Builds a matrix filled with the results of a function applied to each of its component coordinates. - #[inline] - pub fn from_fn N>(nrows: usize, ncols: usize, mut f: F) -> DMatrix { - DMatrix { - nrows: nrows, - ncols: ncols, - mij: (0 .. nrows * ncols).map(|i| { let m = i / nrows; f(i - m * nrows, m) }).collect() - } - } - - /// Transforms this matrix into an array. This consumes the matrix and is O(1). - /// The returned vector contains the matrix data in column-major order. - #[inline] - pub fn into_vector(self) -> Vec { - self.mij - } -} - -dmat_impl!(DMatrix, DVector); - - -/// A stack-allocated dynamically sized matrix with at most one row and column. -pub struct DMatrix1 { - nrows: usize, - ncols: usize, - mij: [N; 1], -} - -small_dmat_impl!(DMatrix1, DVector1, 1, 0); -small_dmat_from_impl!(DMatrix1, 1, ::zero()); - - -/// A stack-allocated dynamically sized square or rectangular matrix with at most 2 rows and columns. -pub struct DMatrix2 { - nrows: usize, - ncols: usize, - mij: [N; 2 * 2], -} - -small_dmat_impl!(DMatrix2, DVector2, 2, 0, 1, - 2, 3); -small_dmat_from_impl!(DMatrix2, 2, ::zero(), ::zero(), - ::zero(), ::zero()); - - -/// A stack-allocated dynamically sized square or rectangular matrix with at most 3 rows and columns. -pub struct DMatrix3 { - nrows: usize, - ncols: usize, - mij: [N; 3 * 3], -} - -small_dmat_impl!(DMatrix3, DVector3, 3, 0, 1, 2, - 3, 4, 5, - 6, 7, 8); -small_dmat_from_impl!(DMatrix3, 3, ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero()); - - -/// A stack-allocated dynamically sized square or rectangular matrix with at most 4 rows and columns. -pub struct DMatrix4 { - nrows: usize, - ncols: usize, - mij: [N; 4 * 4], -} - -small_dmat_impl!(DMatrix4, DVector4, 4, 0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, 10, 11, - 12, 13, 14, 15); -small_dmat_from_impl!(DMatrix4, 4, ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero()); - - -/// A stack-allocated dynamically sized square or rectangular matrix with at most 5 rows and columns. -pub struct DMatrix5 { - nrows: usize, - ncols: usize, - mij: [N; 5 * 5], -} - -small_dmat_impl!(DMatrix5, DVector5, 5, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24); -small_dmat_from_impl!(DMatrix5, 5, ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero()); - - -/// A stack-allocated dynamically sized square or rectangular matrix with at most 6 rows and columns. -pub struct DMatrix6 { - nrows: usize, - ncols: usize, - mij: [N; 6 * 6], -} - -small_dmat_impl!(DMatrix6, DVector6, 6, 0, 1, 2, 3, 4, 5, - 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35); -small_dmat_from_impl!(DMatrix6, 6, ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), - ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero()); diff --git a/src/structs/dmatrix_macros.rs b/src/structs/dmatrix_macros.rs deleted file mode 100644 index a50e23c6..00000000 --- a/src/structs/dmatrix_macros.rs +++ /dev/null @@ -1,1186 +0,0 @@ -#![macro_use] - -macro_rules! dmat_impl( - ($dmatrix: ident, $dvector: ident) => ( - impl $dmatrix { - /// Builds a matrix filled with zeros. - /// - /// # Arguments - /// * `dimension` - The dimension of the matrix. A `dimension`-dimensional matrix contains `dimension * dimension` - /// components. - #[inline] - pub fn new_zeros(nrows: usize, ncols: usize) -> $dmatrix { - $dmatrix::from_element(nrows, ncols, ::zero()) - } - - /// Tests if all components of the matrix are zeroes. - #[inline] - pub fn is_zero(&self) -> bool { - self.mij.iter().all(|e| e.is_zero()) - } - - /// Set this matrix components to zero. - #[inline] - pub fn reset(&mut self) { - for mij in self.mij.iter_mut() { - *mij = ::zero(); - } - } - } - - impl $dmatrix { - /// Builds a matrix filled with random values. - #[inline] - pub fn new_random(nrows: usize, ncols: usize) -> $dmatrix { - $dmatrix::from_fn(nrows, ncols, |_, _| rand::random()) - } - } - - impl $dmatrix { - /// Builds a matrix filled with a given constant. - #[inline] - pub fn new_ones(nrows: usize, ncols: usize) -> $dmatrix { - $dmatrix::from_element(nrows, ncols, ::one()) - } - } - - impl $dmatrix { - /// The number of row on the matrix. - #[inline] - pub fn nrows(&self) -> usize { - self.nrows - } - - /// The number of columns on the matrix. - #[inline] - pub fn ncols(&self) -> usize { - self.ncols - } - - /// Gets a reference to this matrix data. - /// The returned vector contains the matrix data in column-major order. - #[inline] - pub fn as_vector(&self) -> &[N] { - &self.mij - } - - /// Gets a mutable reference to this matrix data. - /// The returned vector contains the matrix data in column-major order. - #[inline] - pub fn as_mut_vector(&mut self) -> &mut [N] { - &mut self.mij[..] - } - } - - // FIXME: add a function to modify the dimension (to avoid useless allocations)? - - impl Eye for $dmatrix { - /// Builds an identity matrix. - /// - /// # Arguments - /// * `dimension` - The dimension of the matrix. A `dimension`-dimensional matrix contains `dimension * dimension` - /// components. - #[inline] - fn new_identity(dimension: usize) -> $dmatrix { - let mut res = $dmatrix::new_zeros(dimension, dimension); - - for i in 0 .. dimension { - res[(i, i)] = ::one::(); - } - - res - } - } - - impl $dmatrix { - #[inline] - fn offset(&self, i: usize, j: usize) -> usize { - i + j * self.nrows - } - - } - - impl Indexable<(usize, usize), N> for $dmatrix { - /// Just like `set` without bounds checking. - #[inline] - unsafe fn unsafe_set(&mut self, rowcol: (usize, usize), val: N) { - let (row, column) = rowcol; - let offset = self.offset(row, column); - *self.mij[..].get_unchecked_mut(offset) = val - } - - /// Just like `at` without bounds checking. - #[inline] - unsafe fn unsafe_at(&self, rowcol: (usize, usize)) -> N { - let (row, column) = rowcol; - - *self.mij.get_unchecked(self.offset(row, column)) - } - - #[inline] - fn swap(&mut self, rowcol1: (usize, usize), rowcol2: (usize, usize)) { - let (row1, col1) = rowcol1; - let (row2, col2) = rowcol2; - let offset1 = self.offset(row1, col1); - let offset2 = self.offset(row2, col2); - let count = self.mij.len(); - assert!(offset1 < count); - assert!(offset2 < count); - self.mij[..].swap(offset1, offset2); - } - - } - - impl Shape<(usize, usize)> for $dmatrix { - #[inline] - fn shape(&self) -> (usize, usize) { - (self.nrows, self.ncols) - } - } - - impl Index<(usize, usize)> for $dmatrix { - type Output = N; - - fn index(&self, (i, j): (usize, usize)) -> &N { - assert!(i < self.nrows); - assert!(j < self.ncols); - - unsafe { - self.mij.get_unchecked(self.offset(i, j)) - } - } - } - - impl IndexMut<(usize, usize)> for $dmatrix { - fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut N { - assert!(i < self.nrows); - assert!(j < self.ncols); - - let offset = self.offset(i, j); - - unsafe { - self.mij[..].get_unchecked_mut(offset) - } - } - } - - /* - * - * Multiplications matrix/matrix. - * - */ - impl Mul<$dmatrix> for $dmatrix - where N: Copy + Mul + Add + Zero { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: $dmatrix) -> $dmatrix { - (&self) * (&right) - } - } - - impl<'a, N> Mul<&'a $dmatrix> for $dmatrix - where N: Copy + Mul + Add + Zero { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: &'a $dmatrix) -> $dmatrix { - (&self) * right - } - } - - impl<'a, N> Mul<$dmatrix> for &'a $dmatrix - where N: Copy + Mul + Add + Zero { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: $dmatrix) -> $dmatrix { - self * (&right) - } - } - - impl<'a, 'b, N> Mul<&'b $dmatrix> for &'a $dmatrix - where N: Copy + Mul + Add + Zero { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: &$dmatrix) -> $dmatrix { - assert!(self.ncols == right.nrows); - - let mut res = unsafe { $dmatrix::new_uninitialized(self.nrows, right.ncols) }; - - for i in 0 .. self.nrows { - for j in 0 .. right.ncols { - let mut acc: N = ::zero(); - - unsafe { - for k in 0 .. self.ncols { - acc = acc + self.unsafe_at((i, k)) * right.unsafe_at((k, j)); - } - - res.unsafe_set((i, j), acc); - } - } - } - - res - } - } - - impl MulAssign<$dmatrix> for $dmatrix - where N: Copy + Mul + Add + Zero { - #[inline] - fn mul_assign(&mut self, right: $dmatrix) { - self.mul_assign(&right) - } - } - - impl<'a, N> MulAssign<&'a $dmatrix> for $dmatrix - where N: Copy + Mul + Add + Zero { - #[inline] - fn mul_assign(&mut self, right: &'a $dmatrix) { - assert!(self.ncols == right.nrows); - - // FIXME: optimize when both matrices have the same layout. - let res = &*self * right; - *self = res; - } - } - - - /* - * - * Multiplication matrix/vector. - * - */ - impl Mul<$dvector> for $dmatrix - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: $dvector) -> $dvector { - (&self) * (&right) - } - } - - impl<'a, N> Mul<$dvector> for &'a $dmatrix - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: $dvector) -> $dvector { - self * (&right) - } - } - - impl<'a, N> Mul<&'a $dvector> for $dmatrix - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: &'a $dvector) -> $dvector { - (&self) * right - } - } - - impl<'a, 'b, N> Mul<&'b $dvector> for &'a $dmatrix - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: &'b $dvector) -> $dvector { - assert!(self.ncols == right.len()); - - let mut res : $dvector = unsafe { $dvector::new_uninitialized(self.nrows) }; - - for i in 0..self.nrows { - let mut acc: N = ::zero(); - - for j in 0..self.ncols { - unsafe { - acc = acc + self.unsafe_at((i, j)) * right.unsafe_at(j); - } - } - - unsafe { - res.unsafe_set(i, acc); - } - } - - res - } - } - - impl Mul<$dmatrix> for $dvector - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: $dmatrix) -> $dvector { - (&self) * (&right) - } - } - - impl<'a, N> Mul<$dmatrix> for &'a $dvector - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: $dmatrix) -> $dvector { - self * (&right) - } - } - - impl<'a, N> Mul<&'a $dmatrix> for $dvector - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: &'a $dmatrix) -> $dvector { - (&self) * right - } - } - - - impl<'a, 'b, N> Mul<&'b $dmatrix> for &'a $dvector - where N: Copy + Add + Mul + Zero { - type Output = $dvector; - - fn mul(self, right: &'b $dmatrix) -> $dvector { - assert!(right.nrows == self.len()); - - let mut res : $dvector = unsafe { $dvector::new_uninitialized(right.ncols) }; - - for i in 0..right.ncols { - let mut acc: N = ::zero(); - - for j in 0..right.nrows { - unsafe { - acc = acc + self.unsafe_at(j) * right.unsafe_at((j, i)); - } - } - - unsafe { - res.unsafe_set(i, acc); - } - } - - res - } - } - - impl MulAssign<$dmatrix> for $dvector - where N: Copy + Mul + Add + Zero { - #[inline] - fn mul_assign(&mut self, right: $dmatrix) { - self.mul_assign(&right) - } - } - - impl<'a, N> MulAssign<&'a $dmatrix> for $dvector - where N: Copy + Mul + Add + Zero { - #[inline] - fn mul_assign(&mut self, right: &'a $dmatrix) { - assert!(right.nrows == self.len()); - - let res = &*self * right; - *self = res; - } - } - - /* - * - * Addition matrix/matrix. - * - */ - impl> Add<$dmatrix> for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn add(self, right: $dmatrix) -> $dmatrix { - self + (&right) - } - } - - impl<'a, N: Copy + Add> Add<$dmatrix> for &'a $dmatrix { - type Output = $dmatrix; - - #[inline] - fn add(self, right: $dmatrix) -> $dmatrix { - right + self - } - } - - impl<'a, N: Copy + Add> Add<&'a $dmatrix> for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn add(self, right: &'a $dmatrix) -> $dmatrix { - let mut res = self; - - for (mij, right_ij) in res.mij.iter_mut().zip(right.mij.iter()) { - *mij = *mij + *right_ij; - } - - res - } - } - - impl> AddAssign<$dmatrix> for $dmatrix { - #[inline] - fn add_assign(&mut self, right: $dmatrix) { - self.add_assign(&right) - } - } - - impl<'a, N: Copy + AddAssign> AddAssign<&'a $dmatrix> for $dmatrix { - #[inline] - fn add_assign(&mut self, right: &'a $dmatrix) { - assert!(self.nrows == right.nrows && self.ncols == right.ncols, - "Unable to add matrices with different dimensions."); - - for (mij, right_ij) in self.mij.iter_mut().zip(right.mij.iter()) { - *mij += *right_ij; - } - } - } - - /* - * - * Subtraction matrix/scalar. - * - */ - impl> Sub for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn sub(self, right: N) -> $dmatrix { - let mut res = self; - - for mij in res.mij.iter_mut() { - *mij = *mij - right; - } - - res - } - } - - impl<'a, N: Copy + SubAssign> SubAssign for $dmatrix { - #[inline] - fn sub_assign(&mut self, right: N) { - for mij in self.mij.iter_mut() { - *mij -= right - } - } - } - - impl Sub<$dmatrix> for f32 { - type Output = $dmatrix; - - #[inline] - fn sub(self, right: $dmatrix) -> $dmatrix { - let mut res = right; - - for mij in res.mij.iter_mut() { - *mij = self - *mij; - } - - res - } - } - - impl Sub<$dmatrix> for f64 { - type Output = $dmatrix; - - #[inline] - fn sub(self, right: $dmatrix) -> $dmatrix { - let mut res = right; - - for mij in res.mij.iter_mut() { - *mij = self - *mij; - } - - res - } - } - - /* - * - * Subtraction matrix/matrix. - * - */ - impl> Sub<$dmatrix> for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn sub(self, right: $dmatrix) -> $dmatrix { - self - (&right) - } - } - - impl<'a, N: Copy + Sub> Sub<$dmatrix> for &'a $dmatrix { - type Output = $dmatrix; - - #[inline] - fn sub(self, right: $dmatrix) -> $dmatrix { - assert!(self.nrows == right.nrows && self.ncols == right.ncols, - "Unable to subtract matrices with different dimensions."); - - let mut res = right; - - for (mij, res) in self.mij.iter().zip(res.mij.iter_mut()) { - *res = *mij - *res; - } - - res - } - } - - impl<'a, N: Copy + Sub> Sub<&'a $dmatrix> for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn sub(self, right: &'a $dmatrix) -> $dmatrix { - assert!(self.nrows == right.nrows && self.ncols == right.ncols, - "Unable to subtract matrices with different dimensions."); - - let mut res = self; - - for (mij, right_ij) in res.mij.iter_mut().zip(right.mij.iter()) { - *mij = *mij - *right_ij; - } - - res - } - } - - impl> SubAssign<$dmatrix> for $dmatrix { - #[inline] - fn sub_assign(&mut self, right: $dmatrix) { - self.sub_assign(&right) - } - } - - impl<'a, N: Copy + SubAssign> SubAssign<&'a $dmatrix> for $dmatrix { - #[inline] - fn sub_assign(&mut self, right: &'a $dmatrix) { - assert!(self.nrows == right.nrows && self.ncols == right.ncols, - "Unable to subtract matrices with different dimensions."); - - for (mij, right_ij) in self.mij.iter_mut().zip(right.mij.iter()) { - *mij -= *right_ij; - } - } - } - - /* - * - * Inversion. - * - */ - impl Inverse for $dmatrix { - #[inline] - fn inverse(&self) -> Option<$dmatrix> { - let mut res: $dmatrix = self.clone(); - if res.inverse_mut() { - Some(res) - } - else { - None - } - } - - fn inverse_mut(&mut self) -> bool { - assert!(self.nrows == self.ncols); - - let dimension = self.nrows; - let mut res: $dmatrix = Eye::new_identity(dimension); - - // inversion using Gauss-Jordan elimination - for k in 0..dimension { - // search a non-zero value on the k-th column - // FIXME: would it be worth it to spend some more time searching for the - // max instead? - - let mut n0 = k; // index of a non-zero entry - - while n0 != dimension { - if unsafe { self.unsafe_at((n0, k)) } != ::zero() { - break; - } - - n0 = n0 + 1; - } - - if n0 == dimension { - return false - } - - // swap pivot line - if n0 != k { - for j in 0..dimension { - let off_n0_j = self.offset(n0, j); - let off_k_j = self.offset(k, j); - - self.mij[..].swap(off_n0_j, off_k_j); - res.mij[..].swap(off_n0_j, off_k_j); - } - } - - unsafe { - let pivot = self.unsafe_at((k, k)); - - for j in k..dimension { - let selfval = self.unsafe_at((k, j)) / pivot; - self.unsafe_set((k, j), selfval); - } - - for j in 0..dimension { - let resval = res.unsafe_at((k, j)) / pivot; - res.unsafe_set((k, j), resval); - } - - for l in 0..dimension { - if l != k { - let normalizer = self.unsafe_at((l, k)); - - for j in k..dimension { - let selfval = self.unsafe_at((l, j)) - self.unsafe_at((k, j)) * normalizer; - self.unsafe_set((l, j), selfval); - } - - for j in 0..dimension { - let resval = res.unsafe_at((l, j)) - res.unsafe_at((k, j)) * normalizer; - res.unsafe_set((l, j), resval); - } - } - } - } - } - - *self = res; - - true - } - } - - impl Transpose for $dmatrix { - #[inline] - fn transpose(&self) -> $dmatrix { - if self.nrows == self.ncols { - let mut res = self.clone(); - - res.transpose_mut(); - - res - } - else { - let mut res = unsafe { $dmatrix::new_uninitialized(self.ncols, self.nrows) }; - - for i in 0..self.nrows { - for j in 0..self.ncols { - unsafe { - res.unsafe_set((j, i), self.unsafe_at((i, j))) - } - } - } - - res - } - } - - #[inline] - fn transpose_mut(&mut self) { - if self.nrows == self.ncols { - let n = self.nrows; - for i in 0..n - 1 { - for j in i + 1..n { - let off_i_j = self.offset(i, j); - let off_j_i = self.offset(j, i); - - self.mij[..].swap(off_i_j, off_j_i); - } - } - } - else { - // FIXME: implement a better algorithm which does that in-place. - *self = Transpose::transpose(self); - } - } - } - - impl + Clone> Mean<$dvector> for $dmatrix { - fn mean(&self) -> $dvector { - let mut res: $dvector = $dvector::new_zeros(self.ncols); - let normalizer: N = Cast::from(1.0f64 / self.nrows as f64); - - for i in 0 .. self.nrows { - for j in 0 .. self.ncols { - unsafe { - let acc = res.unsafe_at(j) + self.unsafe_at((i, j)) * normalizer; - res.unsafe_set(j, acc); - } - } - } - - res - } - } - - impl + Clone> Covariance<$dmatrix> for $dmatrix { - // FIXME: this could be heavily optimized, removing all temporaries by merging loops. - fn covariance(&self) -> $dmatrix { - assert!(self.nrows > 1); - - let mut centered = unsafe { $dmatrix::new_uninitialized(self.nrows, self.ncols) }; - let mean = self.mean(); - - // FIXME: use the rows iterator when available - for i in 0 .. self.nrows { - for j in 0 .. self.ncols { - unsafe { - centered.unsafe_set((i, j), self.unsafe_at((i, j)) - mean.unsafe_at(j)); - } - } - } - - // FIXME: return a triangular matrix? - let fnormalizer: f64 = Cast::from(self.nrows() - 1); - let normalizer: N = Cast::from(fnormalizer); - - // FIXME: this will do 2 allocations for temporaries! - (Transpose::transpose(¢ered) * centered) / normalizer - } - } - - impl Column<$dvector> for $dmatrix { - #[inline] - fn ncols(&self) -> usize { - self.ncols - } - - #[inline] - fn set_column(&mut self, column_id: usize, column: $dvector) { - assert!(column_id < self.ncols); - assert!(column.len() == self.nrows); - - for row_id in 0 .. self.nrows { - unsafe { - self.unsafe_set((row_id, column_id), column.unsafe_at(row_id)); - } - } - } - - fn column(&self, column_id: usize) -> $dvector { - assert!(column_id < self.ncols); - - let start = self.offset(0, column_id); - let stop = self.offset(self.nrows, column_id); - $dvector::from_slice(self.nrows, &self.mij[start .. stop]) - } - } - - impl ColumnSlice<$dvector> for $dmatrix { - fn column_slice(&self, column_id :usize, row_start: usize, row_end: usize) -> $dvector { - assert!(column_id < self.ncols); - assert!(row_start < row_end); - assert!(row_end <= self.nrows); - - // We can init from slice thanks to the matrix being column-major. - let start = self.offset(row_start, column_id); - let stop = self.offset(row_end, column_id); - - $dvector::from_slice(row_end - row_start, &self.mij[start .. stop]) - } - } - - impl Row<$dvector> for $dmatrix { - #[inline] - fn nrows(&self) -> usize { - self.nrows - } - - #[inline] - fn set_row(&mut self, row_id: usize, row: $dvector) { - assert!(row_id < self.nrows); - assert!(row.len() == self.ncols); - - for column_id in 0 .. self.ncols { - unsafe { - self.unsafe_set((row_id, column_id), row.unsafe_at(column_id)); - } - } - } - - #[inline] - fn row(&self, row_id: usize) -> $dvector { - assert!(row_id < self.nrows); - - let mut slice : $dvector = unsafe { - $dvector::new_uninitialized(self.ncols) - }; - - for column_id in 0 .. self.ncols { - unsafe { - slice.unsafe_set(column_id, self.unsafe_at((row_id, column_id))); - } - } - slice - } - } - - impl RowSlice<$dvector> for $dmatrix { - fn row_slice(&self, row_id :usize, column_start: usize, column_end: usize) -> $dvector { - assert!(row_id < self.nrows); - assert!(column_start < column_end); - assert!(column_end <= self.ncols); - - let mut slice : $dvector = unsafe { - $dvector::new_uninitialized(column_end - column_start) - }; - - for column_id in column_start .. column_end { - unsafe { - let slice_idx = column_id - column_start; - slice.unsafe_set(slice_idx, self.unsafe_at((row_id, column_id))); - } - } - - slice - } - } - - impl Diagonal<$dvector> for $dmatrix { - #[inline] - fn from_diagonal(diagonal: &$dvector) -> $dmatrix { - let mut res = $dmatrix::new_zeros(diagonal.len(), diagonal.len()); - - res.set_diagonal(diagonal); - - res - } - - #[inline] - fn diagonal(&self) -> $dvector { - let smallest_dim = cmp::min(self.nrows, self.ncols); - - let mut diagonal: $dvector = $dvector::new_zeros(smallest_dim); - - for i in 0..smallest_dim { - unsafe { diagonal.unsafe_set(i, self.unsafe_at((i, i))) } - } - - diagonal - } - } - - impl DiagonalMut<$dvector> for $dmatrix { - #[inline] - fn set_diagonal(&mut self, diagonal: &$dvector) { - let smallest_dim = cmp::min(self.nrows, self.ncols); - - assert!(diagonal.len() == smallest_dim); - - for i in 0..smallest_dim { - unsafe { self.unsafe_set((i, i), diagonal.unsafe_at(i)) } - } - } - } - - impl> ApproxEq for $dmatrix { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq_eps(&self, other: &$dmatrix, epsilon: &N) -> bool { - let mut zip = self.mij.iter().zip(other.mij.iter()); - zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon)) - } - - #[inline] - fn approx_eq_ulps(&self, other: &$dmatrix, ulps: u32) -> bool { - let mut zip = self.mij.iter().zip(other.mij.iter()); - zip.all(|(a, b)| ApproxEq::approx_eq_ulps(a, b, ulps)) - } - } - - impl Debug for $dmatrix { - fn fmt(&self, form:&mut Formatter) -> Result { - for i in 0..self.nrows() { - for j in 0..self.ncols() { - let _ = write!(form, "{:?} ", self[(i, j)]); - } - let _ = write!(form, "\n"); - } - write!(form, "\n") - } - } - - /* - * - * Multpilication matrix/scalar. - * - */ - impl> Mul for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: N) -> $dmatrix { - let mut res = self; - - for mij in res.mij.iter_mut() { - *mij = *mij * right; - } - - res - } - } - - impl Mul<$dmatrix> for f32 { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: $dmatrix) -> $dmatrix { - let mut res = right; - - for mij in res.mij.iter_mut() { - *mij = self * *mij; - } - - res - } - } - - impl Mul<$dmatrix> for f64 { - type Output = $dmatrix; - - #[inline] - fn mul(self, right: $dmatrix) -> $dmatrix { - let mut res = right; - - for mij in res.mij.iter_mut() { - *mij = self * *mij; - } - - res - } - } - - /* - * - * Division matrix/scalar. - * - */ - impl> Div for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn div(self, right: N) -> $dmatrix { - let mut res = self; - - for mij in res.mij.iter_mut() { - *mij = *mij / right; - } - - res - } - } - - - /* - * - * Addition matrix/scalar. - * - */ - impl> Add for $dmatrix { - type Output = $dmatrix; - - #[inline] - fn add(self, right: N) -> $dmatrix { - let mut res = self; - - for mij in res.mij.iter_mut() { - *mij = *mij + right; - } - - res - } - } - - impl Add<$dmatrix> for f32 { - type Output = $dmatrix; - - #[inline] - fn add(self, right: $dmatrix) -> $dmatrix { - let mut res = right; - - for mij in res.mij.iter_mut() { - *mij = self + *mij; - } - - res - } - } - - impl Add<$dmatrix> for f64 { - type Output = $dmatrix; - - #[inline] - fn add(self, right: $dmatrix) -> $dmatrix { - let mut res = right; - - for mij in res.mij.iter_mut() { - *mij = self + *mij; - } - - res - } - } - - #[cfg(feature="arbitrary")] - impl Arbitrary for $dmatrix { - fn arbitrary(g: &mut G) -> $dmatrix { - $dmatrix::from_fn( - Arbitrary::arbitrary(g), Arbitrary::arbitrary(g), - |_, _| Arbitrary::arbitrary(g) - ) - } - } - ) -); - -macro_rules! small_dmat_impl ( - ($dmatrix: ident, $dvector: ident, $dimension: expr, $($idx: expr),*) => ( - impl PartialEq for $dmatrix { - #[inline] - fn eq(&self, other: &$dmatrix) -> bool { - if self.nrows() != other.nrows() || self.ncols() != other.ncols() { - return false; // FIXME: fail instead? - } - - for (a, b) in self.mij[0 .. self.nrows() * self.ncols()].iter().zip( - other.mij[0 .. self.nrows() * self.ncols()].iter()) { - if *a != *b { - return false; - } - } - - true - } - } - - impl Clone for $dmatrix { - fn clone(&self) -> $dmatrix { - let mij: [N; $dimension * $dimension] = [ $( self.mij[$idx].clone(), )* ]; - - $dmatrix { - nrows: self.nrows, - ncols: self.ncols, - mij: mij, - } - } - } - - dmat_impl!($dmatrix, $dvector); - ) -); - -macro_rules! small_dmat_from_impl( - ($dmatrix: ident, $dimension: expr, $($zeros: expr),*) => ( - impl $dmatrix { - /// Builds a matrix filled with a given constant. - #[inline] - pub fn from_element(nrows: usize, ncols: usize, elem: N) -> $dmatrix { - assert!(nrows <= $dimension); - assert!(ncols <= $dimension); - - let mut mij: [N; $dimension * $dimension] = [ $( $zeros, )* ]; - - for n in &mut mij[.. nrows * ncols] { - *n = elem; - } - - $dmatrix { - nrows: nrows, - ncols: ncols, - mij: mij - } - } - - /// Builds a matrix filled with the components provided by a vector. - /// The vector contains the matrix data in row-major order. - /// Note that `from_column_vector` is a lot faster than `from_row_vector` since a `$dmatrix` stores its data - /// in column-major order. - /// - /// The vector must have at least `nrows * ncols` elements. - #[inline] - pub fn from_row_vector(nrows: usize, ncols: usize, vector: &[N]) -> $dmatrix { - let mut res = $dmatrix::from_column_vector(ncols, nrows, vector); - - // we transpose because the buffer is row_major - res.transpose_mut(); - - res - } - - /// Builds a matrix filled with the components provided by a vector. - /// The vector contains the matrix data in column-major order. - /// Note that `from_column_vector` is a lot faster than `from_row_vector` since a `$dmatrix` stores its data - /// in column-major order. - /// - /// The vector must have at least `nrows * ncols` elements. - #[inline] - pub fn from_column_vector(nrows: usize, ncols: usize, vector: &[N]) -> $dmatrix { - assert!(nrows * ncols == vector.len()); - - let mut mij: [N; $dimension * $dimension] = [ $( $zeros, )* ]; - - for (n, val) in mij[.. nrows * ncols].iter_mut().zip(vector.iter()) { - *n = *val; - } - - $dmatrix { - nrows: nrows, - ncols: ncols, - mij: mij - } - } - - /// Builds a matrix using an initialization function. - #[inline] - pub fn from_fn N>(nrows: usize, ncols: usize, mut f: F) -> $dmatrix { - assert!(nrows <= $dimension); - assert!(ncols <= $dimension); - - let mut mij: [N; $dimension * $dimension] = [ $( $zeros, )* ]; - - for i in 0 .. nrows { - for j in 0 .. ncols { - mij[i + j * nrows] = f(i, j) - } - } - - $dmatrix { - nrows: nrows, - ncols: ncols, - mij: mij - } - } - } - - impl $dmatrix { - /// Creates a new matrix with uninitialized components (with `mem::uninitialized()`). - #[inline] - pub unsafe fn new_uninitialized(nrows: usize, ncols: usize) -> $dmatrix { - assert!(nrows <= $dimension); - assert!(ncols <= $dimension); - - $dmatrix { - nrows: nrows, - ncols: ncols, - mij: mem::uninitialized() - } - } - } - ) -); diff --git a/src/structs/dvector.rs b/src/structs/dvector.rs deleted file mode 100644 index 62761272..00000000 --- a/src/structs/dvector.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Vector with dimensions unknown at compile-time. - -use std::slice::{Iter, IterMut}; -use std::iter::{FromIterator, IntoIterator}; -use std::iter::repeat; -use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign, Index, IndexMut}; -use std::mem; -use rand::{self, Rand}; -use num::{Zero, One}; -use structs::DMatrix; -use traits::operations::{ApproxEq, Axpy, Mean, Outer}; -use traits::geometry::{Dot, Norm}; -use traits::structure::{Iterable, IterableMut, Indexable, Shape, BaseFloat, BaseNum, Cast}; -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -/// Heap allocated, dynamically sized vector. -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct DVector { - /// Components of the vector. Contains as much elements as the vector dimension. - pub at: Vec -} - -impl DVector { - /// Creates an uninitialized vector. - #[inline] - pub unsafe fn new_uninitialized(dimension: usize) -> DVector { - let mut vector = Vec::with_capacity(dimension); - vector.set_len(dimension); - - DVector { - at: vector - } - } -} - -impl DVector { - /// Builds a vector filled with a constant. - #[inline] - pub fn from_element(dimension: usize, elem: N) -> DVector { - DVector { at: repeat(elem).take(dimension).collect() } - } - - /// Builds a vector filled with the components provided by a vector. - /// - /// The vector must have at least `dimension` elements. - #[inline] - pub fn from_slice(dimension: usize, vector: &[N]) -> DVector { - assert!(dimension <= vector.len()); - - DVector { - at: vector[.. dimension].to_vec() - } - } -} - -impl DVector { - /// Builds a vector filled with the results of a function applied to each of its component coordinates. - #[inline] - pub fn from_fn N>(dimension: usize, f: F) -> DVector { - DVector { at: (0 .. dimension).map(f).collect() } - } - - /// The vector length. - #[inline] - pub fn len(&self) -> usize { - self.at.len() - } -} - -impl FromIterator for DVector { - #[inline] - fn from_iter>(param: I) -> DVector { - DVector { at: param.into_iter().collect() } - } -} - -impl Outer for DVector { - type OuterProductType = DMatrix; - - #[inline] - fn outer(&self, other: &DVector) -> DMatrix { - let mut res = unsafe { DMatrix::new_uninitialized(self.at.len(), other.at.len()) }; - - for i in 0 .. self.at.len() { - for j in 0 .. other.at.len() { - unsafe { - res.unsafe_set((i, j), self.unsafe_at(i) * other.unsafe_at(j)); - } - } - } - - res - } -} - -#[cfg(feature="arbitrary")] -impl Arbitrary for DVector { - fn arbitrary(g: &mut G) -> DVector { - DVector { at: Arbitrary::arbitrary(g) } - } -} - - -dvec_impl!(DVector); - -/// Stack-allocated, dynamically sized vector with a maximum size of 1. -pub struct DVector1 { - at: [N; 1], - dimension: usize -} - -small_dvec_impl!(DVector1, 1, 0); -small_dvec_from_impl!(DVector1, 1, ::zero()); - - -/// Stack-allocated, dynamically sized vector with a maximum size of 2. -pub struct DVector2 { - at: [N; 2], - dimension: usize -} - -small_dvec_impl!(DVector2, 2, 0, 1); -small_dvec_from_impl!(DVector2, 2, ::zero(), ::zero()); - - -/// Stack-allocated, dynamically sized vector with a maximum size of 3. -pub struct DVector3 { - at: [N; 3], - dimension: usize -} - -small_dvec_impl!(DVector3, 3, 0, 1, 2); -small_dvec_from_impl!(DVector3, 3, ::zero(), ::zero(), ::zero()); - - -/// Stack-allocated, dynamically sized vector with a maximum size of 4. -pub struct DVector4 { - at: [N; 4], - dimension: usize -} - -small_dvec_impl!(DVector4, 4, 0, 1, 2, 3); -small_dvec_from_impl!(DVector4, 4, ::zero(), ::zero(), ::zero(), ::zero()); - - -/// Stack-allocated, dynamically sized vector with a maximum size of 5. -pub struct DVector5 { - at: [N; 5], - dimension: usize -} - -small_dvec_impl!(DVector5, 5, 0, 1, 2, 3, 4); -small_dvec_from_impl!(DVector5, 5, ::zero(), ::zero(), ::zero(), ::zero(), ::zero()); - - -/// Stack-allocated, dynamically sized vector with a maximum size of 6. -pub struct DVector6 { - at: [N; 6], - dimension: usize -} - -small_dvec_impl!(DVector6, 6, 0, 1, 2, 3, 4, 5); -small_dvec_from_impl!(DVector6, 6, ::zero(), ::zero(), ::zero(), ::zero(), ::zero(), ::zero()); diff --git a/src/structs/dvector_macros.rs b/src/structs/dvector_macros.rs deleted file mode 100644 index 12c685b6..00000000 --- a/src/structs/dvector_macros.rs +++ /dev/null @@ -1,243 +0,0 @@ -#![macro_use] - -macro_rules! dvec_impl( - ($dvector: ident) => ( - vecn_dvec_common_impl!($dvector); - - impl $dvector { - /// Builds a vector filled with zeros. - /// - /// # Arguments - /// * `dimension` - The dimension of the vector. - #[inline] - pub fn new_zeros(dimension: usize) -> $dvector { - $dvector::from_element(dimension, ::zero()) - } - } - - impl $dvector { - /// Builds a vector filled with ones. - /// - /// # Arguments - /// * `dimension` - The dimension of the vector. - #[inline] - pub fn new_ones(dimension: usize) -> $dvector { - $dvector::from_element(dimension, ::one()) - } - } - - impl $dvector { - /// Builds a vector filled with random values. - #[inline] - pub fn new_random(dimension: usize) -> $dvector { - $dvector::from_fn(dimension, |_| rand::random()) - } - } - - impl> $dvector { - /// Computes the canonical basis for the given dimension. A canonical basis is a set of - /// vectors, mutually orthogonal, with all its component equal to 0.0 except one which is equal - /// to 1.0. - pub fn canonical_basis_with_dimension(dimension: usize) -> Vec<$dvector> { - let mut res : Vec<$dvector> = Vec::new(); - - for i in 0 .. dimension { - let mut basis_element : $dvector = $dvector::new_zeros(dimension); - - basis_element[i] = ::one(); - - res.push(basis_element); - } - - res - } - - /// Computes a basis of the space orthogonal to the vector. If the input vector is of dimension - /// `n`, this will return `n - 1` vectors. - pub fn orthogonal_subspace_basis(&self) -> Vec<$dvector> { - // compute the basis of the orthogonal subspace using Gram-Schmidt - // orthogonalization algorithm - let dimension = self.len(); - let mut res : Vec<$dvector> = Vec::new(); - - for i in 0 .. dimension { - let mut basis_element : $dvector = $dvector::new_zeros(self.len()); - - basis_element[i] = ::one(); - - if res.len() == dimension - 1 { - break; - } - - let mut elt = basis_element.clone(); - - elt.axpy(&-::dot(&basis_element, self), self); - - for v in res.iter() { - let proj = ::dot(&elt, v); - elt.axpy(&-proj, v) - }; - - if !ApproxEq::approx_eq(&Norm::norm_squared(&elt), &::zero()) { - res.push(Norm::normalize(&elt)); - } - } - - assert!(res.len() == dimension - 1); - - res - } - } - ) -); - -macro_rules! small_dvec_impl ( - ($dvector: ident, $dimension: expr, $($idx: expr),*) => ( - dvec_impl!($dvector); - - impl $dvector { - /// The number of elements of this vector. - #[inline] - pub fn len(&self) -> usize { - self.dimension - } - - /// Creates an uninitialized vector of dimension `dimension`. - #[inline] - pub unsafe fn new_uninitialized(dimension: usize) -> $dvector { - assert!(dimension <= $dimension, "The chosen dimension is too high for that type of \ - stack-allocated dynamic vector. Consider using the \ - heap-allocated vector: DVector."); - - $dvector { - at: mem::uninitialized(), - dimension: dimension - } - } - } - - impl PartialEq for $dvector { - #[inline] - fn eq(&self, other: &$dvector) -> bool { - if self.len() != other.len() { - return false; // FIXME: fail instead? - } - - for (a, b) in self.as_ref().iter().zip(other.as_ref().iter()) { - if *a != *b { - return false; - } - } - - true - } - } - - impl Clone for $dvector { - fn clone(&self) -> $dvector { - let at: [N; $dimension] = [ $( self.at[$idx].clone(), )* ]; - - $dvector { - at: at, - dimension: self.dimension - } - } - } - ) -); - -macro_rules! small_dvec_from_impl ( - ($dvector: ident, $dimension: expr, $($zeros: expr),*) => ( - impl $dvector { - /// Builds a vector filled with a constant. - #[inline] - pub fn from_element(dimension: usize, elem: N) -> $dvector { - assert!(dimension <= $dimension); - - let mut at: [N; $dimension] = [ $( $zeros, )* ]; - - for n in &mut at[.. dimension] { - *n = elem; - } - - $dvector { - at: at, - dimension: dimension - } - } - } - - impl $dvector { - /// Builds a vector filled with the components provided by a vector. - /// - /// The vector must have at least `dimension` elements. - #[inline] - pub fn from_slice(dimension: usize, vector: &[N]) -> $dvector { - assert!(dimension <= vector.len() && dimension <= $dimension); - - // FIXME: not safe. - let mut at: [N; $dimension] = [ $( $zeros, )* ]; - - for (curr, other) in vector.iter().zip(at.iter_mut()) { - *other = *curr; - } - - $dvector { - at: at, - dimension: dimension - } - } - } - - impl $dvector { - /// Builds a vector filled with the result of a function. - #[inline] - pub fn from_fn N>(dimension: usize, mut f: F) -> $dvector { - assert!(dimension <= $dimension); - - let mut at: [N; $dimension] = [ $( $zeros, )* ]; - - for i in 0 .. dimension { - at[i] = f(i); - } - - $dvector { - at: at, - dimension: dimension - } - } - } - - impl FromIterator for $dvector { - #[inline] - fn from_iter>(param: I) -> $dvector { - let mut at: [N; $dimension] = [ $( $zeros, )* ]; - - let mut dimension = 0; - - for n in param.into_iter() { - if dimension == $dimension { - break; - } - - at[dimension] = n; - - dimension = dimension + 1; - } - - $dvector { - at: at, - dimension: dimension - } - } - } - - #[cfg(feature="arbitrary")] - impl Arbitrary for $dvector { - #[inline] - fn arbitrary(g: &mut G) -> $dvector { - $dvector::from_fn(g.gen_range(0, $dimension), |_| Arbitrary::arbitrary(g)) - } - } - ) -); diff --git a/src/structs/isometry.rs b/src/structs/isometry.rs deleted file mode 100644 index 7b9ceb8e..00000000 --- a/src/structs/isometry.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::fmt; -use std::ops::{Add, Sub, Mul, Neg, MulAssign}; - -use rand::{Rand, Rng}; -use num::One; -use structs::matrix::{Matrix3, Matrix4}; -use traits::structure::{Cast, Dimension, Column, BaseFloat, BaseNum}; -use traits::operations::{Inverse, ApproxEq}; -use traits::geometry::{RotationMatrix, Rotation, Rotate, AbsoluteRotate, Transform, Transformation, - Translate, Translation, ToHomogeneous}; -use structs::vector::{Vector1, Vector2, Vector3}; -use structs::point::{Point2, Point3}; -use structs::rotation::{Rotation2, Rotation3}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - - -/// Two dimensional **direct** isometry. -/// -/// This is the composition of a rotation followed by a translation. Vectors `Vector2` are not -/// affected by the translational component of this transformation while points `Point2` are. -/// Isometries conserve angles and distances, hence do not allow shearing nor scaling. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Isometry2 { - /// The rotation applicable by this isometry. - pub rotation: Rotation2, - /// The translation applicable by this isometry. - pub translation: Vector2 -} - -/// Three dimensional **direct** isometry. -/// -/// This is the composition of a rotation followed by a translation. Vectors `Vector3` are not -/// affected by the translational component of this transformation while points `Point3` are. -/// Isometries conserve angles and distances, hence do not allow shearing nor scaling. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Isometry3 { - /// The rotation applicable by this isometry. - pub rotation: Rotation3, - /// The translation applicable by this isometry. - pub translation: Vector3 -} - -impl Isometry3 { - /// Creates an isometry that corresponds to the local frame of an observer standing at the - /// point `eye` and looking toward `target`. - /// - /// It maps the view direction `target - eye` to the positive `z` axis and the origin to the - /// `eye`. - /// - /// # Arguments - /// * eye - The observer position. - /// * target - The target position. - /// * up - Vertical direction. The only requirement of this parameter is to not be collinear - /// to `eye - at`. Non-collinearity is not checked. - #[inline] - pub fn new_observer_frame(eye: &Point3, target: &Point3, up: &Vector3) -> Isometry3 { - let new_rotation_matrix = Rotation3::new_observer_frame(&(*target - *eye), up); - Isometry3::from_rotation_matrix(eye.to_vector(), new_rotation_matrix) - } - - /// Builds a right-handed look-at view matrix. - /// - /// This conforms to the common notion of right handed look-at matrix from the computer - /// graphics community. - /// - /// # Arguments - /// * eye - The eye position. - /// * target - The target position. - /// * up - A vector approximately aligned with required the vertical axis. The only - /// requirement of this parameter is to not be collinear to `target - eye`. - #[inline] - pub fn look_at_rh(eye: &Point3, target: &Point3, up: &Vector3) -> Isometry3 { - let rotation = Rotation3::look_at_rh(&(*target - *eye), up); - let trans = rotation * (-*eye); - - Isometry3::from_rotation_matrix(trans.to_vector(), rotation) - } - - /// Builds a left-handed look-at view matrix. - /// - /// This conforms to the common notion of left handed look-at matrix from the computer - /// graphics community. - /// - /// # Arguments - /// * eye - The eye position. - /// * target - The target position. - /// * up - A vector approximately aligned with required the vertical axis. The only - /// requirement of this parameter is to not be collinear to `target - eye`. - #[inline] - pub fn look_at_lh(eye: &Point3, target: &Point3, up: &Vector3) -> Isometry3 { - let rotation = Rotation3::look_at_lh(&(*target - *eye), up); - let trans = rotation * (-*eye); - - Isometry3::from_rotation_matrix(trans.to_vector(), rotation) - } -} - -isometry_impl!(Isometry2, Rotation2, Vector2, Vector1, Point2, Matrix3); -dim_impl!(Isometry2, 2); - -isometry_impl!(Isometry3, Rotation3, Vector3, Vector3, Point3, Matrix4); -dim_impl!(Isometry3, 3); diff --git a/src/structs/isometry_macros.rs b/src/structs/isometry_macros.rs deleted file mode 100644 index 6000c01a..00000000 --- a/src/structs/isometry_macros.rs +++ /dev/null @@ -1,481 +0,0 @@ -#![macro_use] - -macro_rules! isometry_impl( - ($t: ident, $rotmatrix: ident, $vector: ident, $rotvector: ident, $point: ident, - $homogeneous: ident) => ( - impl $t { - /// Creates a new isometry from an axis-angle rotation, and a vector. - #[inline] - pub fn new(translation: $vector, rotation: $rotvector) -> $t { - $t { - rotation: $rotmatrix::new(rotation), - translation: translation - } - } - - /// Creates a new isometry from a rotation matrix and a vector. - #[inline] - pub fn from_rotation_matrix(translation: $vector, rotation: $rotmatrix) -> $t { - $t { - rotation: rotation, - translation: translation - } - } - } - - - /* - * - * RotationMatrix - * - */ - impl + BaseFloat> - RotationMatrix, $rotvector> for $t { - type Output = $rotmatrix; - - #[inline] - fn to_rotation_matrix(&self) -> $rotmatrix { - self.rotation - } - } - - - /* - * - * One - * - */ - impl One for $t { - #[inline] - fn one() -> $t { - $t::from_rotation_matrix(::zero(), ::one()) - } - } - - - /* - * - * Isometry × Isometry - * - */ - impl Mul<$t> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t::from_rotation_matrix( - self.translation + self.rotation * right.translation, - self.rotation * right.rotation) - } - } - - impl MulAssign<$t> for $t { - #[inline] - fn mul_assign(&mut self, right: $t) { - self.translation += self.rotation * right.translation; - self.rotation *= right.rotation; - } - } - - - /* - * - * Isometry × Rotation - * - */ - impl Mul<$rotmatrix> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $rotmatrix) -> $t { - $t::from_rotation_matrix(self.translation, self.rotation * right) - } - } - - impl Mul<$t> for $rotmatrix { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t::from_rotation_matrix( - self * right.translation, - self * right.rotation) - } - } - - impl MulAssign<$rotmatrix> for $t { - #[inline] - fn mul_assign(&mut self, right: $rotmatrix) { - self.rotation *= right - } - } - - - /* - * - * Isometry × Point - * - */ - impl Mul<$point> for $t { - type Output = $point; - - #[inline] - fn mul(self, right: $point) -> $point { - self.rotation * right + self.translation - } - } - - - /* - * - * Isometry × Vector - * - */ - impl Mul<$vector> for $t { - type Output = $vector; - - #[inline] - fn mul(self, right: $vector) -> $vector { - self.rotation * right - } - } - - - /* - * - * Translation - * - */ - impl Translation<$vector> for $t { - #[inline] - fn translation(&self) -> $vector { - self.translation - } - - #[inline] - fn inverse_translation(&self) -> $vector { - -self.translation - } - - #[inline] - fn append_translation_mut(&mut self, t: &$vector) { - self.translation = *t + self.translation - } - - #[inline] - fn append_translation(&self, t: &$vector) -> $t { - $t::from_rotation_matrix(*t + self.translation, self.rotation) - } - - #[inline] - fn prepend_translation_mut(&mut self, t: &$vector) { - self.translation = self.translation + self.rotation * *t - } - - #[inline] - fn prepend_translation(&self, t: &$vector) -> $t { - $t::from_rotation_matrix(self.translation + self.rotation * *t, self.rotation) - } - - #[inline] - fn set_translation(&mut self, t: $vector) { - self.translation = t - } - } - - - /* - * - * Translate - * - */ - impl + Sub> Translate<$point> for $t { - #[inline] - fn translate(&self, v: &$point) -> $point { - *v + self.translation - } - - #[inline] - fn inverse_translate(&self, v: &$point) -> $point { - *v - self.translation - } - } - - - /* - * - * Rotation - * - */ - impl + BaseFloat> Rotation<$rotvector> for $t { - #[inline] - fn rotation(&self) -> $rotvector { - self.rotation.rotation() - } - - #[inline] - fn inverse_rotation(&self) -> $rotvector { - self.rotation.inverse_rotation() - } - - #[inline] - fn append_rotation_mut(&mut self, rotation: &$rotvector) { - let delta = $rotmatrix::new(*rotation); - - self.rotation = delta * self.rotation; - self.translation = delta * self.translation; - } - - #[inline] - fn append_rotation(&self, rotation: &$rotvector) -> $t { - let delta = $rotmatrix::new(*rotation); - - $t::from_rotation_matrix(delta * self.translation, delta * self.rotation) - } - - #[inline] - fn prepend_rotation_mut(&mut self, rotation: &$rotvector) { - let delta = $rotmatrix::new(*rotation); - - self.rotation = self.rotation * delta; - } - - #[inline] - fn prepend_rotation(&self, rotation: &$rotvector) -> $t { - let delta = $rotmatrix::new(*rotation); - - $t::from_rotation_matrix(self.translation, self.rotation * delta) - } - - #[inline] - fn set_rotation(&mut self, rotation: $rotvector) { - // FIXME: should the translation be changed too? - self.rotation.set_rotation(rotation) - } - } - - - /* - * - * Rotate - * - */ - impl Rotate<$vector> for $t { - #[inline] - fn rotate(&self, v: &$vector) -> $vector { - self.rotation.rotate(v) - } - - #[inline] - fn inverse_rotate(&self, v: &$vector) -> $vector { - self.rotation.inverse_rotate(v) - } - } - - - /* - * - * Transformation - * - */ - impl Transformation<$t> for $t { - fn transformation(&self) -> $t { - *self - } - - fn inverse_transformation(&self) -> $t { - // inversion will never fails - Inverse::inverse(self).unwrap() - } - - fn append_transformation_mut(&mut self, t: &$t) { - *self = *t * *self - } - - fn append_transformation(&self, t: &$t) -> $t { - *t * *self - } - - fn prepend_transformation_mut(&mut self, t: &$t) { - *self = *self * *t - } - - fn prepend_transformation(&self, t: &$t) -> $t { - *self * *t - } - - fn set_transformation(&mut self, t: $t) { - *self = t - } - } - - - /* - * - * Transform - * - */ - impl Transform<$point> for $t { - #[inline] - fn transform(&self, p: &$point) -> $point { - self.rotation.transform(p) + self.translation - } - - #[inline] - fn inverse_transform(&self, p: &$point) -> $point { - self.rotation.inverse_transform(&(*p - self.translation)) - } - } - - - /* - * - * Inverse - * - */ - impl> Inverse for $t { - #[inline] - fn inverse_mut(&mut self) -> bool { - self.rotation.inverse_mut(); - self.translation = self.rotation * -self.translation; - // always succeed - true - } - - #[inline] - fn inverse(&self) -> Option<$t> { - let mut res = *self; - res.inverse_mut(); - // always succeed - Some(res) - } - } - - - /* - * - * ToHomogeneous - * - */ - impl ToHomogeneous<$homogeneous> for $t { - fn to_homogeneous(&self) -> $homogeneous { - let mut res = self.rotation.to_homogeneous(); - - // copy the translation - let dimension = Dimension::dimension(None::<$homogeneous>); - - res.set_column(dimension - 1, self.translation.as_point().to_homogeneous().to_vector()); - - res - } - } - - - /* - * - * ApproxEq - * - */ - impl> ApproxEq for $t { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N) -> bool { - ApproxEq::approx_eq_eps(&self.rotation, &other.rotation, epsilon) && - ApproxEq::approx_eq_eps(&self.translation, &other.translation, epsilon) - } - - #[inline] - fn approx_eq_ulps(&self, other: &$t, ulps: u32) -> bool { - ApproxEq::approx_eq_ulps(&self.rotation, &other.rotation, ulps) && - ApproxEq::approx_eq_ulps(&self.translation, &other.translation, ulps) - } - } - - - /* - * - * Rand - * - */ - impl Rand for $t { - #[inline] - fn rand(rng: &mut R) -> $t { - $t::new(rng.gen(), rng.gen()) - } - } - - - /* - * - * AbsoluteRotate - * - */ - impl AbsoluteRotate<$vector> for $t { - #[inline] - fn absolute_rotate(&self, v: &$vector) -> $vector { - self.rotation.absolute_rotate(v) - } - } - - - /* - * - * Arbitrary - * - */ - #[cfg(feature="arbitrary")] - impl Arbitrary for $t { - fn arbitrary(g: &mut G) -> $t { - $t::from_rotation_matrix( - Arbitrary::arbitrary(g), - Arbitrary::arbitrary(g) - ) - } - } - - - /* - * - * Display - * - */ - impl fmt::Display for $t { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(writeln!(f, "Isometry {{")); - - if let Some(precision) = f.precision() { - try!(writeln!(f, "... translation: {:.*}", precision, self.translation)); - try!(writeln!(f, "... rotation matrix:")); - try!(write!(f, "{:.*}", precision, *self.rotation.submatrix())); - } - else { - try!(writeln!(f, "... translation: {}", self.translation)); - try!(writeln!(f, "... rotation matrix:")); - try!(write!(f, "{}", *self.rotation.submatrix())); - } - - writeln!(f, "}}") - } - } - ) -); - -macro_rules! dim_impl( - ($t: ident, $dimension: expr) => ( - impl Dimension for $t { - #[inline] - fn dimension(_: Option<$t>) -> usize { - $dimension - } - } - ) -); diff --git a/src/structs/matrix.rs b/src/structs/matrix.rs deleted file mode 100644 index 9b44fe31..00000000 --- a/src/structs/matrix.rs +++ /dev/null @@ -1,290 +0,0 @@ -//! Matrixrices with dimensions known at compile-time. - -#![allow(missing_docs)] // we allow missing to avoid having to document the mij components. - -use std::fmt; -use std::ops::{Add, Sub, Mul, Div, AddAssign, SubAssign, MulAssign, DivAssign, Index, IndexMut}; -use std::mem; -use std::slice::{Iter, IterMut}; -use rand::{Rand, Rng}; -use num::{Zero, One}; -use traits::operations::ApproxEq; -use structs::vector::{Vector1, Vector2, Vector3, Vector4, Vector5, Vector6}; -use structs::point::{Point1, Point4, Point5, Point6}; -use structs::dvector::{DVector1, DVector2, DVector3, DVector4, DVector5, DVector6}; - -use traits::structure::{Cast, Row, Column, Iterable, IterableMut, Dimension, Indexable, Eye, ColumnSlice, - RowSlice, Diagonal, DiagonalMut, Shape, BaseFloat, BaseNum, Repeat}; -use traits::operations::{Absolute, Transpose, Inverse, Outer, EigenQR, Mean}; -use traits::geometry::{ToHomogeneous, FromHomogeneous, Origin}; -use linalg; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -#[cfg(feature="abstract_algebra")] -use_matrix_group_modules!(); - - -/// Special identity matrix. All its operation are no-ops. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Identity; - -impl Identity { - /// Creates a new identity matrix. - #[inline] - pub fn new() -> Identity { - Identity - } -} - -impl fmt::Display for Identity { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Identity") - } -} - -/// Square matrix of dimension 1. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Matrix1 { - pub m11: N -} - -eye_impl!(Matrix1, 1, m11); - -matrix_impl!(Matrix1, 1, Vector1, DVector1, m11); -one_impl!(Matrix1, ::one); -dim_impl!(Matrix1, 1); -mat_mul_mat_impl!(Matrix1, 1); -mat_mul_vec_impl!(Matrix1, Vector1, 1, ::zero); -vec_mul_mat_impl!(Matrix1, Vector1, 1, ::zero); -mat_mul_point_impl!(Matrix1, Point1, 1, Origin::origin); -point_mul_mat_impl!(Matrix1, Point1, 1, Origin::origin); -// (specialized); inverse_impl!(Matrix1, 1); -to_homogeneous_impl!(Matrix1, Matrix2, 1, 2); -from_homogeneous_impl!(Matrix1, Matrix2, 1, 2); -eigen_qr_impl!(Matrix1, Vector1); -componentwise_arbitrary!(Matrix1, m11); -componentwise_rand!(Matrix1, m11); -mat_display_impl!(Matrix1, 1); - -/// Square matrix of dimension 2. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Matrix2 { - pub m11: N, pub m21: N, - pub m12: N, pub m22: N -} - -eye_impl!(Matrix2, 2, m11, m22); - -matrix_impl!(Matrix2, 2, Vector2, DVector2, m11, m12, - m21, m22); -one_impl!(Matrix2, ::one, ::zero, - ::zero, ::one); -dim_impl!(Matrix2, 2); -// (specialized); mul_impl!(Matrix2, 2); -// (specialized); rmul_impl!(Matrix2, Vector2, 2); -// (specialized); lmul_impl!(Matrix2, Vector2, 2); -// (specialized); inverse_impl!(Matrix2, 2); -to_homogeneous_impl!(Matrix2, Matrix3, 2, 3); -from_homogeneous_impl!(Matrix2, Matrix3, 2, 3); -eigen_qr_impl!(Matrix2, Vector2); -componentwise_arbitrary!(Matrix2, m11, m12, m21, m22); -componentwise_rand!(Matrix2, m11, m12, m21, m22); -mat_display_impl!(Matrix2, 2); - -/// Square matrix of dimension 3. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Matrix3 { - pub m11: N, pub m21: N, pub m31: N, - pub m12: N, pub m22: N, pub m32: N, - pub m13: N, pub m23: N, pub m33: N -} - -eye_impl!(Matrix3, 3, m11, m22, m33); - -matrix_impl!(Matrix3, 3, Vector3, DVector3, m11, m12, m13, - m21, m22, m23, - m31, m32, m33); -one_impl!(Matrix3, ::one , ::zero, ::zero, - ::zero, ::one , ::zero, - ::zero, ::zero, ::one); -dim_impl!(Matrix3, 3); -// (specialized); mul_impl!(Matrix3, 3); -// (specialized); rmul_impl!(Matrix3, Vector3, 3); -// (specialized); lmul_impl!(Matrix3, Vector3, 3); -// (specialized); inverse_impl!(Matrix3, 3); -to_homogeneous_impl!(Matrix3, Matrix4, 3, 4); -from_homogeneous_impl!(Matrix3, Matrix4, 3, 4); -eigen_qr_impl!(Matrix3, Vector3); -componentwise_arbitrary!(Matrix3, - m11, m12, m13, - m21, m22, m23, - m31, m32, m33 -); -componentwise_rand!(Matrix3, - m11, m12, m13, - m21, m22, m23, - m31, m32, m33 -); -mat_display_impl!(Matrix3, 3); - -/// Square matrix of dimension 4. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Matrix4 { - pub m11: N, pub m21: N, pub m31: N, pub m41: N, - pub m12: N, pub m22: N, pub m32: N, pub m42: N, - pub m13: N, pub m23: N, pub m33: N, pub m43: N, - pub m14: N, pub m24: N, pub m34: N, pub m44: N -} - -eye_impl!(Matrix4, 4, m11, m22, m33, m44); - -matrix_impl!(Matrix4, 4, Vector4, DVector4, - m11, m12, m13, m14, - m21, m22, m23, m24, - m31, m32, m33, m34, - m41, m42, m43, m44 -); -one_impl!(Matrix4, ::one , ::zero, ::zero, ::zero, - ::zero, ::one , ::zero, ::zero, - ::zero, ::zero, ::one , ::zero, - ::zero, ::zero, ::zero, ::one); -dim_impl!(Matrix4, 4); -mat_mul_mat_impl!(Matrix4, 4); -mat_mul_vec_impl!(Matrix4, Vector4, 4, ::zero); -vec_mul_mat_impl!(Matrix4, Vector4, 4, ::zero); -mat_mul_point_impl!(Matrix4, Point4, 4, Origin::origin); -point_mul_mat_impl!(Matrix4, Point4, 4, Origin::origin); -inverse_impl!(Matrix4, 4); -to_homogeneous_impl!(Matrix4, Matrix5, 4, 5); -from_homogeneous_impl!(Matrix4, Matrix5, 4, 5); -eigen_qr_impl!(Matrix4, Vector4); -componentwise_arbitrary!(Matrix4, - m11, m12, m13, m14, - m21, m22, m23, m24, - m31, m32, m33, m34, - m41, m42, m43, m44 -); -componentwise_rand!(Matrix4, - m11, m12, m13, m14, - m21, m22, m23, m24, - m31, m32, m33, m34, - m41, m42, m43, m44 -); -mat_display_impl!(Matrix4, 4); - -/// Square matrix of dimension 5. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Matrix5 { - pub m11: N, pub m21: N, pub m31: N, pub m41: N, pub m51: N, - pub m12: N, pub m22: N, pub m32: N, pub m42: N, pub m52: N, - pub m13: N, pub m23: N, pub m33: N, pub m43: N, pub m53: N, - pub m14: N, pub m24: N, pub m34: N, pub m44: N, pub m54: N, - pub m15: N, pub m25: N, pub m35: N, pub m45: N, pub m55: N -} - -eye_impl!(Matrix5, 5, m11, m22, m33, m44, m55); - -matrix_impl!(Matrix5, 5, Vector5, DVector5, - m11, m12, m13, m14, m15, - m21, m22, m23, m24, m25, - m31, m32, m33, m34, m35, - m41, m42, m43, m44, m45, - m51, m52, m53, m54, m55 -); -one_impl!(Matrix5, - ::one , ::zero, ::zero, ::zero, ::zero, - ::zero, ::one , ::zero, ::zero, ::zero, - ::zero, ::zero, ::one , ::zero, ::zero, - ::zero, ::zero, ::zero, ::one , ::zero, - ::zero, ::zero, ::zero, ::zero, ::one -); -dim_impl!(Matrix5, 5); -mat_mul_mat_impl!(Matrix5, 5); -mat_mul_vec_impl!(Matrix5, Vector5, 5, ::zero); -vec_mul_mat_impl!(Matrix5, Vector5, 5, ::zero); -mat_mul_point_impl!(Matrix5, Point5, 5, Origin::origin); -point_mul_mat_impl!(Matrix5, Point5, 5, Origin::origin); -inverse_impl!(Matrix5, 5); -to_homogeneous_impl!(Matrix5, Matrix6, 5, 6); -from_homogeneous_impl!(Matrix5, Matrix6, 5, 6); -eigen_qr_impl!(Matrix5, Vector5); -componentwise_arbitrary!(Matrix5, - m11, m12, m13, m14, m15, - m21, m22, m23, m24, m25, - m31, m32, m33, m34, m35, - m41, m42, m43, m44, m45, - m51, m52, m53, m54, m55 -); -componentwise_rand!(Matrix5, - m11, m12, m13, m14, m15, - m21, m22, m23, m24, m25, - m31, m32, m33, m34, m35, - m41, m42, m43, m44, m45, - m51, m52, m53, m54, m55 -); -mat_display_impl!(Matrix5, 5); - -/// Square matrix of dimension 6. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Matrix6 { - pub m11: N, pub m21: N, pub m31: N, pub m41: N, pub m51: N, pub m61: N, - pub m12: N, pub m22: N, pub m32: N, pub m42: N, pub m52: N, pub m62: N, - pub m13: N, pub m23: N, pub m33: N, pub m43: N, pub m53: N, pub m63: N, - pub m14: N, pub m24: N, pub m34: N, pub m44: N, pub m54: N, pub m64: N, - pub m15: N, pub m25: N, pub m35: N, pub m45: N, pub m55: N, pub m65: N, - pub m16: N, pub m26: N, pub m36: N, pub m46: N, pub m56: N, pub m66: N -} - -eye_impl!(Matrix6, 6, m11, m22, m33, m44, m55, m66); - -matrix_impl!(Matrix6, 6, Vector6, DVector6, - m11, m12, m13, m14, m15, m16, - m21, m22, m23, m24, m25, m26, - m31, m32, m33, m34, m35, m36, - m41, m42, m43, m44, m45, m46, - m51, m52, m53, m54, m55, m56, - m61, m62, m63, m64, m65, m66 -); - -one_impl!(Matrix6, - ::one , ::zero, ::zero, ::zero, ::zero, ::zero, - ::zero, ::one , ::zero, ::zero, ::zero, ::zero, - ::zero, ::zero, ::one , ::zero, ::zero, ::zero, - ::zero, ::zero, ::zero, ::one , ::zero, ::zero, - ::zero, ::zero, ::zero, ::zero, ::one , ::zero, - ::zero, ::zero, ::zero, ::zero, ::zero, ::one -); -dim_impl!(Matrix6, 6); -mat_mul_mat_impl!(Matrix6, 6); -mat_mul_vec_impl!(Matrix6, Vector6, 6, ::zero); -vec_mul_mat_impl!(Matrix6, Vector6, 6, ::zero); -mat_mul_point_impl!(Matrix6, Point6, 6, Origin::origin); -point_mul_mat_impl!(Matrix6, Point6, 6, Origin::origin); -inverse_impl!(Matrix6, 6); -eigen_qr_impl!(Matrix6, Vector6); -componentwise_arbitrary!(Matrix6, - m11, m12, m13, m14, m15, m16, - m21, m22, m23, m24, m25, m26, - m31, m32, m33, m34, m35, m36, - m41, m42, m43, m44, m45, m46, - m51, m52, m53, m54, m55, m56, - m61, m62, m63, m64, m65, m66 -); -componentwise_rand!(Matrix6, - m11, m12, m13, m14, m15, m16, - m21, m22, m23, m24, m25, m26, - m31, m32, m33, m34, m35, m36, - m41, m42, m43, m44, m45, m46, - m51, m52, m53, m54, m55, m56, - m61, m62, m63, m64, m65, m66 -); -mat_display_impl!(Matrix6, 6); diff --git a/src/structs/matrix_macros.rs b/src/structs/matrix_macros.rs deleted file mode 100644 index 4f3d12ee..00000000 --- a/src/structs/matrix_macros.rs +++ /dev/null @@ -1,741 +0,0 @@ -#![macro_use] - -macro_rules! matrix_impl( - ($t: ident, $dimension: expr, $vector: ident, $dvector: ident, $($compN: ident),+) => ( - - matrix_group_approx_impl!($t, $($compN),+); - - impl $t { - #[inline] - pub fn new($($compN: N ),+) -> $t { - $t { - $($compN: $compN ),+ - } - } - } - - - /* - * - * Conversions (AsRef, AsMut, From) - * - */ - impl AsRef<[[N; $dimension]; $dimension]> for $t { - #[inline] - fn as_ref(&self) -> &[[N; $dimension]; $dimension] { - unsafe { - mem::transmute(self) - } - } - } - - impl AsMut<[[N; $dimension]; $dimension]> for $t { - #[inline] - fn as_mut(&mut self) -> &mut [[N; $dimension]; $dimension] { - unsafe { - mem::transmute(self) - } - } - } - - impl<'a, N> From<&'a [[N; $dimension]; $dimension]> for &'a $t { - #[inline] - fn from(arr: &'a [[N; $dimension]; $dimension]) -> &'a $t { - unsafe { - mem::transmute(arr) - } - } - } - - impl<'a, N> From<&'a mut [[N; $dimension]; $dimension]> for &'a mut $t { - #[inline] - fn from(arr: &'a mut [[N; $dimension]; $dimension]) -> &'a mut $t { - unsafe { - mem::transmute(arr) - } - } - } - - impl<'a, N: Clone> From<&'a [[N; $dimension]; $dimension]> for $t { - #[inline] - fn from(arr: &'a [[N; $dimension]; $dimension]) -> $t { - let tref: &$t = From::from(arr); - tref.clone() - } - } - - - /* - * - * Unsafe indexing. - * - */ - impl $t { - #[inline] - pub unsafe fn at_fast(&self, (i, j): (usize, usize)) -> N { - (*mem::transmute::<&$t, &[N; $dimension * $dimension]>(self) - .get_unchecked(i + j * $dimension)) - } - - #[inline] - pub unsafe fn set_fast(&mut self, (i, j): (usize, usize), val: N) { - (*mem::transmute::<&mut $t, &mut [N; $dimension * $dimension]>(self) - .get_unchecked_mut(i + j * $dimension)) = val - } - } - - - /* - * - * Cast - * - */ - impl> Cast<$t> for $t { - #[inline] - fn from(v: $t) -> $t { - $t::new($(Cast::from(v.$compN)),+) - } - } - - - /* - * - * Iterable - * - */ - impl Iterable for $t { - #[inline] - fn iter(&self) -> Iter { - unsafe { - mem::transmute::<&$t, &[N; $dimension * $dimension]>(self).iter() - } - } - } - - impl IterableMut for $t { - #[inline] - fn iter_mut(& mut self) -> IterMut { - unsafe { - mem::transmute::<&mut $t, &mut [N; $dimension * $dimension]>(self).iter_mut() - } - } - } - - - /* - * - * Shape/Indexable/Index - * - */ - impl Shape<(usize, usize)> for $t { - #[inline] - fn shape(&self) -> (usize, usize) { - ($dimension, $dimension) - } - } - - impl Indexable<(usize, usize), N> for $t { - #[inline] - fn swap(&mut self, (i1, j1): (usize, usize), (i2, j2): (usize, usize)) { - unsafe { - mem::transmute::<&mut $t, &mut [N; $dimension * $dimension]>(self) - .swap(i1 + j1 * $dimension, i2 + j2 * $dimension) - } - } - - #[inline] - unsafe fn unsafe_at(&self, (i, j): (usize, usize)) -> N { - (*mem::transmute::<&$t, &[N; $dimension * $dimension]>(self).get_unchecked(i + j * $dimension)) - } - - #[inline] - unsafe fn unsafe_set(&mut self, (i, j): (usize, usize), val: N) { - (*mem::transmute::<&mut $t, &mut [N; $dimension * $dimension]>(self).get_unchecked_mut(i + j * $dimension)) = val - } - } - - impl Index<(usize, usize)> for $t { - type Output = N; - - fn index(&self, (i, j): (usize, usize)) -> &N { - unsafe { - &mem::transmute::<&$t, & [N; $dimension * $dimension]>(self)[i + j * $dimension] - } - } - } - - impl IndexMut<(usize, usize)> for $t { - fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut N { - unsafe { - &mut mem::transmute::<&mut $t, &mut [N; $dimension * $dimension]>(self)[i + j * $dimension] - } - } - } - - - /* - * - * Row/Column - * - */ - impl Column<$vector> for $t { - #[inline] - fn ncols(&self) -> usize { - Dimension::dimension(None::<$t>) - } - - #[inline] - fn set_column(&mut self, column: usize, v: $vector) { - for (i, e) in v.iter().enumerate() { - self[(i, column)] = *e; - } - } - - #[inline] - fn column(&self, column: usize) -> $vector { - let mut res: $vector = ::zero(); - - for (i, e) in res.iter_mut().enumerate() { - *e = self[(i, column)]; - } - - res - } - } - - impl ColumnSlice<$dvector> for $t { - fn column_slice(&self, cid: usize, rstart: usize, rend: usize) -> $dvector { - let column = self.column(cid); - - $dvector::from_slice(rend - rstart, &column.as_ref()[rstart .. rend]) - } - } - - impl Row<$vector> for $t { - #[inline] - fn nrows(&self) -> usize { - Dimension::dimension(None::<$t>) - } - - #[inline] - fn set_row(&mut self, row: usize, v: $vector) { - for (i, e) in v.iter().enumerate() { - self[(row, i)] = *e; - } - } - - #[inline] - fn row(&self, row: usize) -> $vector { - let mut res: $vector = ::zero(); - - for (i, e) in res.iter_mut().enumerate() { - *e = self[(row, i)]; - } - - res - } - } - - impl RowSlice<$dvector> for $t { - fn row_slice(&self, rid: usize, cstart: usize, cend: usize) -> $dvector { - let row = self.row(rid); - - $dvector::from_slice(cend - cstart, &row.as_ref()[cstart .. cend]) - } - } - - - /* - * - * Transpose - * - */ - impl Transpose for $t { - #[inline] - fn transpose(&self) -> $t { - let mut res = *self; - - res.transpose_mut(); - res - } - - #[inline] - fn transpose_mut(&mut self) { - for i in 1 .. $dimension { - for j in 0 .. i { - self.swap((i, j), (j, i)) - } - } - } - } - - - /* - * - * ApproxEq - * - */ - impl> ApproxEq for $t { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N) -> bool { - let mut zip = self.iter().zip(other.iter()); - zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon)) - } - - #[inline] - fn approx_eq_ulps(&self, other: &$t, ulps: u32) -> bool { - let mut zip = self.iter().zip(other.iter()); - zip.all(|(a, b)| ApproxEq::approx_eq_ulps(a, b, ulps)) - } - } - - - /* - * - * Mean - * - */ - impl + Clone> Mean<$vector> for $t { - fn mean(&self) -> $vector { - let mut res: $vector = ::zero(); - let normalizer: N = Cast::from(1.0f64 / $dimension as f64); - - for i in 0 .. $dimension { - for j in 0 .. $dimension { - unsafe { - let acc = res.unsafe_at(j) + self.unsafe_at((i, j)) * normalizer; - res.unsafe_set(j, acc); - } - } - } - - res - } - } - - - /* - * - * Diagonal - * - */ - impl Diagonal<$vector> for $t { - #[inline] - fn from_diagonal(diagonal: &$vector) -> $t { - let mut res: $t = ::zero(); - - res.set_diagonal(diagonal); - - res - } - - #[inline] - fn diagonal(&self) -> $vector { - let mut diagonal: $vector = ::zero(); - - for i in 0 .. $dimension { - unsafe { diagonal.unsafe_set(i, self.unsafe_at((i, i))) } - } - - diagonal - } - } - - impl DiagonalMut<$vector> for $t { - #[inline] - fn set_diagonal(&mut self, diagonal: &$vector) { - for i in 0 .. $dimension { - unsafe { self.unsafe_set((i, i), diagonal.unsafe_at(i)) } - } - } - } - - - /* - * - * Outer - * - */ - impl + Zero> Outer for $vector { - type OuterProductType = $t; - - #[inline] - fn outer(&self, other: &$vector) -> $t { - let mut res: $t = ::zero(); - - for i in 0 .. ::dimension::<$vector>() { - for j in 0 .. ::dimension::<$vector>() { - res[(i, j)] = self[i] * other[j] - } - } - res - } - } - - /* - * - * Componentwise unary operations. - * - */ - componentwise_repeat!($t, $($compN),+); - componentwise_absolute!($t, $($compN),+); - componentwise_zero!($t, $($compN),+); - - /* - * - * Pointwise binary operations. - * - */ - pointwise_add!($t, $($compN),+); - pointwise_sub!($t, $($compN),+); - pointwise_scalar_add!($t, $($compN),+); - pointwise_scalar_sub!($t, $($compN),+); - pointwise_scalar_div!($t, $($compN),+); - pointwise_scalar_mul!($t, $($compN),+); - ) -); - - -macro_rules! mat_mul_mat_impl( - ($t: ident, $dimension: expr) => ( - impl Mul<$t> for $t { - type Output = $t; - #[inline] - fn mul(self, right: $t) -> $t { - let mut res: $t = ::zero(); - - for i in 0 .. $dimension { - for j in 0 .. $dimension { - let mut acc: N = ::zero(); - - unsafe { - for k in 0 .. $dimension { - acc = acc + self.at_fast((i, k)) * right.at_fast((k, j)); - } - - res.set_fast((i, j), acc); - } - } - } - - res - } - } - - impl MulAssign<$t> for $t { - #[inline] - fn mul_assign(&mut self, right: $t) { - // NOTE: there is probably not any useful optimization to perform here compaired to the - // version without assignment.. - *self = *self * right - } - } - ) -); - -macro_rules! vec_mul_mat_impl( - ($t: ident, $v: ident, $dimension: expr, $zero: expr) => ( - impl Mul<$t> for $v { - type Output = $v; - - #[inline] - fn mul(self, right: $t) -> $v { - let mut res : $v = $zero(); - - for i in 0..$dimension { - for j in 0..$dimension { - unsafe { - let val = res.at_fast(i) + self.at_fast(j) * right.at_fast((j, i)); - res.set_fast(i, val) - } - } - } - - res - } - } - - impl MulAssign<$t> for $v { - #[inline] - fn mul_assign(&mut self, right: $t) { - // NOTE: there is probably not any useful optimization to perform here compaired to the - // version without assignment.. - *self = *self * right - } - } - ) -); - -macro_rules! mat_mul_vec_impl( - ($t: ident, $v: ident, $dimension: expr, $zero: expr) => ( - impl Mul<$v> for $t { - type Output = $v; - - #[inline] - fn mul(self, right: $v) -> $v { - let mut res : $v = $zero(); - - for i in 0 .. $dimension { - for j in 0 .. $dimension { - unsafe { - let val = res.at_fast(i) + self.at_fast((i, j)) * right.at_fast(j); - res.set_fast(i, val) - } - } - } - - res - } - } - ) -); - -macro_rules! point_mul_mat_impl( - ($t: ident, $v: ident, $dimension: expr, $zero: expr) => ( - vec_mul_mat_impl!($t, $v, $dimension, $zero); - ) -); - -macro_rules! mat_mul_point_impl( - ($t: ident, $v: ident, $dimension: expr, $zero: expr) => ( - mat_mul_vec_impl!($t, $v, $dimension, $zero); - ) -); - -macro_rules! inverse_impl( - ($t: ident, $dimension: expr) => ( - impl - Inverse for $t { - #[inline] - fn inverse(&self) -> Option<$t> { - let mut res : $t = *self; - if res.inverse_mut() { - Some(res) - } - else { - None - } - } - - fn inverse_mut(&mut self) -> bool { - let mut res: $t = ::one(); - - // inversion using Gauss-Jordan elimination - for k in 0..$dimension { - // search a non-zero value on the k-th column - // FIXME: would it be worth it to spend some more time searching for the - // max instead? - - let mut n0 = k; // index of a non-zero entry - - while n0 != $dimension { - if self[(n0, k)] != ::zero() { - break; - } - - n0 = n0 + 1; - } - - if n0 == $dimension { - return false - } - - // swap pivot line - if n0 != k { - for j in 0..$dimension { - self.swap((n0, j), (k, j)); - res.swap((n0, j), (k, j)); - } - } - - let pivot = self[(k, k)]; - - for j in k..$dimension { - let selfval = self[(k, j)] / pivot; - self[(k, j)] = selfval; - } - - for j in 0..$dimension { - let resval = res[(k, j)] / pivot; - res[(k, j)] = resval; - } - - for l in 0..$dimension { - if l != k { - let normalizer = self[(l, k)]; - - for j in k..$dimension { - let selfval = self[(l, j)] - self[(k, j)] * normalizer; - self[(l, j)] = selfval; - } - - for j in 0..$dimension { - let resval = res[(l, j)] - res[(k, j)] * normalizer; - res[(l, j)] = resval; - } - } - } - } - - *self = res; - - true - } - } - ) -); - - -macro_rules! to_homogeneous_impl( - ($t: ident, $t2: ident, $dimension: expr, $dim2: expr) => ( - impl ToHomogeneous<$t2> for $t { - #[inline] - fn to_homogeneous(&self) -> $t2 { - let mut res: $t2 = ::one(); - - for i in 0 .. $dimension { - for j in 0 .. $dimension { - res[(i, j)] = self[(i, j)] - } - } - - res - } - } - ) -); - -macro_rules! from_homogeneous_impl( - ($t: ident, $t2: ident, $dimension: expr, $dim2: expr) => ( - impl FromHomogeneous<$t2> for $t { - #[inline] - fn from(m: &$t2) -> $t { - let mut res: $t = ::one(); - - for i in 0 .. $dimension { - for j in 0 .. $dimension { - res[(i, j)] = m[(i, j)] - } - } - - // FIXME: do we have to deal the lost components - // (like if the 1 is not a 1… do we have to divide?) - - res - } - } - ) -); - - -macro_rules! eigen_qr_impl( - ($t: ident, $v: ident) => ( - impl EigenQR> for $t - where N: BaseFloat + ApproxEq + Clone { - fn eigen_qr(&self, eps: &N, niter: usize) -> ($t, $v) { - linalg::eigen_qr(self, eps, niter) - } - } - ) -); - - - -macro_rules! mat_display_impl( - ($t: ident, $dimension: expr) => ( - impl fmt::Display for $t { - // XXX: will will not always work correctly due to rounding errors. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fn integral_length(val: &N) -> usize { - let mut res = 1; - let mut curr: N = ::cast(10.0f64); - - while curr <= *val { - curr = curr * ::cast(10.0f64); - res = res + 1; - } - - if val.is_sign_negative() { - res + 1 - } - else { - res - } - } - - let mut max_decimal_length = 0; - let mut decimal_lengths: $t = ::zero(); - for i in 0 .. $dimension { - for j in 0 .. $dimension { - decimal_lengths[(i, j)] = integral_length(&self[(i, j)].clone()); - max_decimal_length = ::max(max_decimal_length, decimal_lengths[(i, j)]); - } - } - - let precision = f.precision().unwrap_or(3); - let max_number_length = max_decimal_length + precision + 1; - - try!(writeln!(f, " ┌ {:>width$} ┐", "", width = max_number_length * $dimension + $dimension - 1)); - - for i in 0 .. $dimension { - try!(write!(f, " │")); - for j in 0 .. $dimension { - let number_length = decimal_lengths[(i, j)] + precision + 1; - let pad = max_number_length - number_length; - try!(write!(f, " {:>thepad$}", "", thepad = pad)); - try!(write!(f, "{:.*}", precision, (*self)[(i, j)])); - } - try!(writeln!(f, " │")); - } - - writeln!(f, " └ {:>width$} ┘", "", width = max_number_length * $dimension + $dimension - 1) - } - } - ) -); - -macro_rules! one_impl( - ($t: ident, $($valueN: expr),+ ) => ( - impl One for $t { - #[inline] - fn one() -> $t { - $t::new($($valueN() ),+) - } - } - ) -); - -macro_rules! eye_impl( - ($t: ident, $dimension: expr, $($comp_diagN: ident),+) => ( - impl Eye for $t { - fn new_identity(dimension: usize) -> $t { - assert!(dimension == $dimension); - let mut eye: $t = ::zero(); - $(eye.$comp_diagN = ::one();)+ - eye - } - } - ) -); - - -macro_rules! dim_impl( - ($t: ident, $dimension: expr) => ( - impl Dimension for $t { - #[inline] - fn dimension(_: Option<$t>) -> usize { - $dimension - } - } - ) -); diff --git a/src/structs/mod.rs b/src/structs/mod.rs deleted file mode 100644 index befc49a7..00000000 --- a/src/structs/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Data structures and implementations. - -pub use self::dmatrix::{DMatrix, DMatrix1, DMatrix2, DMatrix3, DMatrix4, DMatrix5, DMatrix6}; -pub use self::dvector::{DVector, DVector1, DVector2, DVector3, DVector4, DVector5, DVector6}; -pub use self::vector::{Vector1, Vector2, Vector3, Vector4, Vector5, Vector6}; -pub use self::point::{Point1, Point2, Point3, Point4, Point5, Point6}; -pub use self::matrix::{Identity, Matrix1, Matrix2, Matrix3, Matrix4, Matrix5, Matrix6}; -pub use self::rotation::{Rotation2, Rotation3}; -pub use self::isometry::{Isometry2, Isometry3}; -pub use self::similarity::{Similarity2, Similarity3}; -pub use self::perspective::{Perspective3, PerspectiveMatrix3}; -pub use self::orthographic::{Orthographic3, OrthographicMatrix3}; -pub use self::quaternion::{Quaternion, UnitQuaternion}; -pub use self::unit::Unit; - -#[cfg(feature="generic_sizes")] -pub use self::vectorn::VectorN; - -mod common_macros; -mod algebra; -mod dmatrix_macros; -mod dmatrix; -mod vectorn_macros; -#[cfg(feature="generic_sizes")] -mod vectorn; -mod dvector_macros; -mod dvector; -mod vector_macros; -mod vector; -mod point_macros; -mod point; -mod quaternion; -mod matrix_macros; -mod matrix; -mod rotation_macros; -mod rotation; -mod isometry_macros; -mod isometry; -mod similarity_macros; -mod similarity; -mod perspective; -mod orthographic; -mod unit; - -// Specialization for some 1d, 2d and 3d operations. -#[doc(hidden)] -mod specializations { - mod identity; - mod matrix; - mod vector; - mod primitives; - // mod complex; -} diff --git a/src/structs/orthographic.rs b/src/structs/orthographic.rs deleted file mode 100644 index 7e355279..00000000 --- a/src/structs/orthographic.rs +++ /dev/null @@ -1,357 +0,0 @@ -use traits::structure::{BaseFloat, Cast}; -use structs::{Point3, Vector3, Matrix4}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - - -/// A 3D orthographic projection stored without any matrix. -/// -/// This flips the `z` axis and maps a axis-aligned cube to the unit cube with corners varying from -/// `(-1, -1, -1)` to `(1, 1, 1)`. Reading or modifying its individual properties is cheap but -/// applying the transformation is costly. -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Orthographic3 { - left: N, - right: N, - bottom: N, - top: N, - znear: N, - zfar: N -} - -/// A 3D orthographic projection stored as a 4D matrix. -/// -/// This flips the `z` axis and maps a axis-aligned cube to the unit cube with corners varying from -/// `(-1, -1, -1)` to `(1, 1, 1)`. Reading or modifying its individual properties is costly but -/// applying the transformation is cheap. -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct OrthographicMatrix3 { - matrix: Matrix4 -} - -impl Orthographic3 { - /// Creates a new 3D orthographic projection. - pub fn new(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> Orthographic3 { - assert!(!::is_zero(&(zfar - znear))); - assert!(!::is_zero(&(left - right))); - assert!(!::is_zero(&(top - bottom))); - - Orthographic3 { - left: left, - right: right, - bottom: bottom, - top: top, - znear: znear, - zfar: zfar - } - } - - /// Builds a 4D projection matrix (using homogeneous coordinates) for this projection. - pub fn to_matrix(&self) -> Matrix4 { - self.to_orthographic_matrix().matrix - } - - /// Build a `OrthographicMatrix3` representing this projection. - pub fn to_orthographic_matrix(&self) -> OrthographicMatrix3 { - OrthographicMatrix3::new(self.left, self.right, self.bottom, self.top, self.znear, self.zfar) - } -} - -#[cfg(feature="arbitrary")] -impl Arbitrary for Orthographic3 { - fn arbitrary(g: &mut G) -> Orthographic3 { - let left = Arbitrary::arbitrary(g); - let right = reject(g, |x: &N| *x > left); - let bottom = Arbitrary::arbitrary(g); - let top = reject(g, |x: &N| *x > bottom); - let znear = Arbitrary::arbitrary(g); - let zfar = reject(g, |x: &N| *x > znear); - Orthographic3::new(left, right, bottom, top, znear, zfar) - } -} - -impl Orthographic3 { - /// The smallest x-coordinate of the view cuboid. - #[inline] - pub fn left(&self) -> N { - self.left - } - - /// The largest x-coordinate of the view cuboid. - #[inline] - pub fn right(&self) -> N { - self.right - } - - /// The smallest y-coordinate of the view cuboid. - #[inline] - pub fn bottom(&self) -> N { - self.bottom - } - - /// The largest y-coordinate of the view cuboid. - #[inline] - pub fn top(&self) -> N { - self.top - } - - /// The near plane offset of the view cuboid. - #[inline] - pub fn znear(&self) -> N { - self.znear - } - - /// The far plane offset of the view cuboid. - #[inline] - pub fn zfar(&self) -> N { - self.zfar - } - - /// Sets the smallest x-coordinate of the view cuboid. - #[inline] - pub fn set_left(&mut self, left: N) { - assert!(left < self.right, "The left corner must be farther than the right corner."); - self.left = left - } - - /// Sets the largest x-coordinate of the view cuboid. - #[inline] - pub fn set_right(&mut self, right: N) { - assert!(right > self.left, "The left corner must be farther than the right corner."); - self.right = right - } - - /// Sets the smallest y-coordinate of the view cuboid. - #[inline] - pub fn set_bottom(&mut self, bottom: N) { - assert!(bottom < self.top, "The top corner must be higher than the bottom corner."); - self.bottom = bottom - } - - /// Sets the largest y-coordinate of the view cuboid. - #[inline] - pub fn set_top(&mut self, top: N) { - assert!(top > self.bottom, "The top corner must be higher than the left corner."); - self.top = top - } - - /// Sets the near plane offset of the view cuboid. - #[inline] - pub fn set_znear(&mut self, znear: N) { - assert!(znear < self.zfar, "The far plane must be farther than the near plane."); - self.znear = znear - } - - /// Sets the far plane offset of the view cuboid. - #[inline] - pub fn set_zfar(&mut self, zfar: N) { - assert!(zfar > self.znear, "The far plane must be farther than the near plane."); - self.zfar = zfar - } - - /// Projects a point. - #[inline] - pub fn project_point(&self, p: &Point3) -> Point3 { - // FIXME: optimize that - self.to_orthographic_matrix().project_point(p) - } - - /// Projects a vector. - #[inline] - pub fn project_vector(&self, p: &Vector3) -> Vector3 { - // FIXME: optimize that - self.to_orthographic_matrix().project_vector(p) - } -} - -impl OrthographicMatrix3 { - /// Creates a new orthographic projection matrix. - pub fn new(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> OrthographicMatrix3 { - assert!(left < right, "The left corner must be farther than the right corner."); - assert!(bottom < top, "The top corner must be higher than the bottom corner."); - assert!(znear < zfar, "The far plane must be farther than the near plane."); - - let matrix: Matrix4 = ::one(); - - let mut res = OrthographicMatrix3 { matrix: matrix }; - res.set_left_and_right(left, right); - res.set_bottom_and_top(bottom, top); - res.set_znear_and_zfar(znear, zfar); - - res - } - - /// Creates a new orthographic projection matrix from an aspect ratio and the vertical field of view. - pub fn from_fov(aspect: N, vfov: N, znear: N, zfar: N) -> OrthographicMatrix3 { - assert!(znear < zfar, "The far plane must be farther than the near plane."); - assert!(!::is_zero(&aspect)); - - let half: N = ::cast(0.5); - let width = zfar * (vfov * half).tan(); - let height = width / aspect; - - OrthographicMatrix3::new(-width * half, width * half, -height * half, height * half, znear, zfar) - } - - /// Creates a new orthographic matrix from a 4D matrix. - #[inline] - pub fn from_matrix_unchecked(matrix: Matrix4) -> OrthographicMatrix3 { - OrthographicMatrix3 { - matrix: matrix - } - } - - /// Returns a reference to the 4D matrix (using homogeneous coordinates) of this projection. - #[inline] - pub fn as_matrix(&self) -> &Matrix4 { - &self.matrix - } - - /// The smallest x-coordinate of the view cuboid. - #[inline] - pub fn left(&self) -> N { - (-::one::() - self.matrix.m14) / self.matrix.m11 - } - - /// The largest x-coordinate of the view cuboid. - #[inline] - pub fn right(&self) -> N { - (::one::() - self.matrix.m14) / self.matrix.m11 - } - - /// The smallest y-coordinate of the view cuboid. - #[inline] - pub fn bottom(&self) -> N { - (-::one::() - self.matrix.m24) / self.matrix.m22 - } - - /// The largest y-coordinate of the view cuboid. - #[inline] - pub fn top(&self) -> N { - (::one::() - self.matrix.m24) / self.matrix.m22 - } - - /// The near plane offset of the view cuboid. - #[inline] - pub fn znear(&self) -> N { - (::one::() + self.matrix.m34) / self.matrix.m33 - } - - /// The far plane offset of the view cuboid. - #[inline] - pub fn zfar(&self) -> N { - (-::one::() + self.matrix.m34) / self.matrix.m33 - } - - /// Sets the smallest x-coordinate of the view cuboid. - #[inline] - pub fn set_left(&mut self, left: N) { - let right = self.right(); - self.set_left_and_right(left, right); - } - - /// Sets the largest x-coordinate of the view cuboid. - #[inline] - pub fn set_right(&mut self, right: N) { - let left = self.left(); - self.set_left_and_right(left, right); - } - - /// Sets the smallest y-coordinate of the view cuboid. - #[inline] - pub fn set_bottom(&mut self, bottom: N) { - let top = self.top(); - self.set_bottom_and_top(bottom, top); - } - - /// Sets the largest y-coordinate of the view cuboid. - #[inline] - pub fn set_top(&mut self, top: N) { - let bottom = self.bottom(); - self.set_bottom_and_top(bottom, top); - } - - /// Sets the near plane offset of the view cuboid. - #[inline] - pub fn set_znear(&mut self, znear: N) { - let zfar = self.zfar(); - self.set_znear_and_zfar(znear, zfar); - } - - /// Sets the far plane offset of the view cuboid. - #[inline] - pub fn set_zfar(&mut self, zfar: N) { - let znear = self.znear(); - self.set_znear_and_zfar(znear, zfar); - } - - /// Sets the view cuboid coordinates along the `x` axis. - #[inline] - pub fn set_left_and_right(&mut self, left: N, right: N) { - assert!(left < right, "The left corner must be farther than the right corner."); - self.matrix.m11 = >::from(2.0) / (right - left); - self.matrix.m14 = -(right + left) / (right - left); - } - - /// Sets the view cuboid coordinates along the `y` axis. - #[inline] - pub fn set_bottom_and_top(&mut self, bottom: N, top: N) { - assert!(bottom < top, "The top corner must be higher than the bottom corner."); - self.matrix.m22 = >::from(2.0) / (top - bottom); - self.matrix.m24 = -(top + bottom) / (top - bottom); - } - - /// Sets the near and far plane offsets of the view cuboid. - #[inline] - pub fn set_znear_and_zfar(&mut self, znear: N, zfar: N) { - assert!(!::is_zero(&(zfar - znear))); - self.matrix.m33 = ->::from(2.0) / (zfar - znear); - self.matrix.m34 = -(zfar + znear) / (zfar - znear); - } - - /// Projects a point. - #[inline] - pub fn project_point(&self, p: &Point3) -> Point3 { - Point3::new( - self.matrix.m11 * p.x + self.matrix.m14, - self.matrix.m22 * p.y + self.matrix.m24, - self.matrix.m33 * p.z + self.matrix.m34 - ) - } - - /// Projects a vector. - #[inline] - pub fn project_vector(&self, p: &Vector3) -> Vector3 { - Vector3::new( - self.matrix.m11 * p.x, - self.matrix.m22 * p.y, - self.matrix.m33 * p.z - ) - } -} - -impl OrthographicMatrix3 { - /// Returns the 4D matrix (using homogeneous coordinates) of this projection. - #[inline] - pub fn to_matrix(&self) -> Matrix4 { - self.matrix - } -} - -#[cfg(feature="arbitrary")] -impl Arbitrary for OrthographicMatrix3 { - fn arbitrary(g: &mut G) -> OrthographicMatrix3 { - let x: Orthographic3 = Arbitrary::arbitrary(g); - x.to_orthographic_matrix() - } -} - - -/// Simple helper function for rejection sampling -#[cfg(feature="arbitrary")] -#[inline] -pub fn reject bool, T: Arbitrary>(g: &mut G, f: F) -> T { - use std::iter::repeat; - repeat(()).map(|_| Arbitrary::arbitrary(g)).filter(f).next().unwrap() -} diff --git a/src/structs/perspective.rs b/src/structs/perspective.rs deleted file mode 100644 index 4788e279..00000000 --- a/src/structs/perspective.rs +++ /dev/null @@ -1,276 +0,0 @@ -use traits::structure::BaseFloat; -use structs::{Point3, Vector3, Matrix4}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - - -/// A 3D perspective projection stored without any matrix. -/// -/// This maps a frustrum cube to the unit cube with corners varying from `(-1, -1, -1)` to -/// `(1, 1, 1)`. Reading or modifying its individual properties is cheap but applying the -/// transformation is costly. -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Perspective3 { - aspect: N, - fovy: N, - znear: N, - zfar: N -} - -/// A 3D perspective projection stored as a 4D matrix. -/// -/// This maps a frustrum to the unit cube with corners varying from `(-1, -1, -1)` to -/// `(1, 1, 1)`. Reading or modifying its individual properties is costly but applying the -/// transformation is cheap. -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct PerspectiveMatrix3 { - matrix: Matrix4 -} - -impl Perspective3 { - /// Creates a new 3D perspective projection. - pub fn new(aspect: N, fovy: N, znear: N, zfar: N) -> Perspective3 { - assert!(!::is_zero(&(zfar - znear))); - assert!(!::is_zero(&aspect)); - - Perspective3 { - aspect: aspect, - fovy: fovy, - znear: znear, - zfar: zfar - } - } - - /// Builds a 4D projection matrix (using homogeneous coordinates) for this projection. - pub fn to_matrix(&self) -> Matrix4 { - self.to_perspective_matrix().matrix - } - - /// Build a `PerspectiveMatrix3` representing this projection. - pub fn to_perspective_matrix(&self) -> PerspectiveMatrix3 { - PerspectiveMatrix3::new(self.aspect, self.fovy, self.znear, self.zfar) - } -} - -#[cfg(feature="arbitrary")] -impl Arbitrary for Perspective3 { - fn arbitrary(g: &mut G) -> Perspective3 { - use structs::orthographic::reject; - let znear = Arbitrary::arbitrary(g); - let zfar = reject(g, |&x: &N| !::is_zero(&(x - znear))); - Perspective3::new(Arbitrary::arbitrary(g), Arbitrary::arbitrary(g), znear, zfar) - } -} - -impl Perspective3 { - /// Gets the `width / height` aspect ratio. - #[inline] - pub fn aspect(&self) -> N { - self.aspect - } - - /// Gets the y field of view of the view frustrum. - #[inline] - pub fn fovy(&self) -> N { - self.fovy - } - - /// Gets the near plane offset of the view frustrum. - #[inline] - pub fn znear(&self) -> N { - self.znear - } - - /// Gets the far plane offset of the view frustrum. - #[inline] - pub fn zfar(&self) -> N { - self.zfar - } - - /// Sets the `width / height` aspect ratio of the view frustrum. - /// - /// This method does not build any matrix. - #[inline] - pub fn set_aspect(&mut self, aspect: N) { - self.aspect = aspect; - } - - /// Sets the y field of view of the view frustrum. - /// - /// This method does not build any matrix. - #[inline] - pub fn set_fovy(&mut self, fovy: N) { - self.fovy = fovy; - } - - /// Sets the near plane offset of the view frustrum. - /// - /// This method does not build any matrix. - #[inline] - pub fn set_znear(&mut self, znear: N) { - self.znear = znear; - } - - /// Sets the far plane offset of the view frustrum. - /// - /// This method does not build any matrix. - #[inline] - pub fn set_zfar(&mut self, zfar: N) { - self.zfar = zfar; - } - - /// Projects a point. - #[inline] - pub fn project_point(&self, p: &Point3) -> Point3 { - // FIXME: optimize that - self.to_perspective_matrix().project_point(p) - } - - /// Projects a vector. - #[inline] - pub fn project_vector(&self, p: &Vector3) -> Vector3 { - // FIXME: optimize that - self.to_perspective_matrix().project_vector(p) - } -} - -impl PerspectiveMatrix3 { - /// Creates a new perspective matrix from the aspect ratio, y field of view, and near/far planes. - pub fn new(aspect: N, fovy: N, znear: N, zfar: N) -> PerspectiveMatrix3 { - assert!(!::is_zero(&(znear - zfar))); - assert!(!::is_zero(&aspect)); - - let matrix: Matrix4 = ::one(); - - let mut res = PerspectiveMatrix3 { matrix: matrix }; - res.set_fovy(fovy); - res.set_aspect(aspect); - res.set_znear_and_zfar(znear, zfar); - res.matrix.m44 = ::zero(); - res.matrix.m43 = -::one::(); - - res - } - - /// Creates a new perspective projection matrix from a 4D matrix. - #[inline] - pub fn from_matrix_unchecked(matrix: Matrix4) -> PerspectiveMatrix3 { - PerspectiveMatrix3 { - matrix: matrix - } - } - - /// Returns a reference to the 4D matrix (using homogeneous coordinates) of this projection. - #[inline] - pub fn as_matrix(&self) -> &Matrix4 { - &self.matrix - } - - /// Gets the `width / height` aspect ratio of the view frustrum. - #[inline] - pub fn aspect(&self) -> N { - self.matrix.m22 / self.matrix.m11 - } - - /// Gets the y field of view of the view frustrum. - #[inline] - pub fn fovy(&self) -> N { - (::one::() / self.matrix.m22).atan() * ::cast(2.0) - } - - /// Gets the near plane offset of the view frustrum. - #[inline] - pub fn znear(&self) -> N { - let ratio = (-self.matrix.m33 + ::one::()) / (-self.matrix.m33 - ::one::()); - - self.matrix.m34 / (ratio * ::cast(2.0)) - self.matrix.m34 / ::cast(2.0) - } - - /// Gets the far plane offset of the view frustrum. - #[inline] - pub fn zfar(&self) -> N { - let ratio = (-self.matrix.m33 + ::one()) / (-self.matrix.m33 - ::one()); - - (self.matrix.m34 - ratio * self.matrix.m34) / ::cast(2.0) - } - - // FIXME: add a method to retrieve znear and zfar simultaneously? - - /// Updates this projection matrix with a new `width / height` aspect ratio of the view - /// frustrum. - #[inline] - pub fn set_aspect(&mut self, aspect: N) { - assert!(!::is_zero(&aspect)); - self.matrix.m11 = self.matrix.m22 / aspect; - } - - /// Updates this projection with a new y field of view of the view frustrum. - #[inline] - pub fn set_fovy(&mut self, fovy: N) { - let old_m22 = self.matrix.m22; - self.matrix.m22 = ::one::() / (fovy / ::cast(2.0)).tan(); - self.matrix.m11 = self.matrix.m11 * (self.matrix.m22 / old_m22); - } - - /// Updates this projection matrix with a new near plane offset of the view frustrum. - #[inline] - pub fn set_znear(&mut self, znear: N) { - let zfar = self.zfar(); - self.set_znear_and_zfar(znear, zfar); - } - - /// Updates this projection matrix with a new far plane offset of the view frustrum. - #[inline] - pub fn set_zfar(&mut self, zfar: N) { - let znear = self.znear(); - self.set_znear_and_zfar(znear, zfar); - } - - /// Updates this projection matrix with new near and far plane offsets of the view frustrum. - #[inline] - pub fn set_znear_and_zfar(&mut self, znear: N, zfar: N) { - self.matrix.m33 = (zfar + znear) / (znear - zfar); - self.matrix.m34 = zfar * znear * ::cast(2.0) / (znear - zfar); - } - - /// Projects a point. - #[inline] - pub fn project_point(&self, p: &Point3) -> Point3 { - let inverse_denom = -::one::() / p.z; - - Point3::new( - self.matrix.m11 * p.x * inverse_denom, - self.matrix.m22 * p.y * inverse_denom, - (self.matrix.m33 * p.z + self.matrix.m34) * inverse_denom - ) - } - - /// Projects a vector. - #[inline] - pub fn project_vector(&self, p: &Vector3) -> Vector3 { - let inverse_denom = -::one::() / p.z; - - Vector3::new( - self.matrix.m11 * p.x * inverse_denom, - self.matrix.m22 * p.y * inverse_denom, - self.matrix.m33 - ) - } -} - -impl PerspectiveMatrix3 { - /// Returns the 4D matrix (using homogeneous coordinates) of this projection. - #[inline] - pub fn to_matrix(&self) -> Matrix4 { - self.matrix - } -} - -#[cfg(feature="arbitrary")] -impl Arbitrary for PerspectiveMatrix3 { - fn arbitrary(g: &mut G) -> PerspectiveMatrix3 { - let x: Perspective3 = Arbitrary::arbitrary(g); - x.to_perspective_matrix() - } -} diff --git a/src/structs/point.rs b/src/structs/point.rs deleted file mode 100644 index ae3e9afc..00000000 --- a/src/structs/point.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! Points with dimension known at compile-time. - -use std::mem; -use std::fmt; -use std::slice::{Iter, IterMut}; -use std::iter::{Iterator, FromIterator, IntoIterator}; -use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign, Index, IndexMut}; -use rand::{Rand, Rng}; -use num::{Zero, One}; -use traits::operations::{ApproxEq, PartialOrder, PartialOrdering, Axpy}; -use traits::structure::{Cast, Dimension, Indexable, Iterable, IterableMut, PointAsVector, Shape, - NumPoint, FloatPoint, BaseFloat, BaseNum, Bounded, Repeat}; -use traits::geometry::{Origin, FromHomogeneous, ToHomogeneous}; -use structs::vector::{Vector1, Vector2, Vector3, Vector4, Vector5, Vector6}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -#[cfg(feature="abstract_algebra")] -use_euclidean_space_modules!(); - - -/// Point of dimension 1. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Point1 { - /// First component of the point. - pub x: N -} - -point_impl!(Point1, Vector1, Point2, y | x); -vectorlike_impl!(Point1, 1, x); -from_iterator_impl!(Point1, iterator); - -/// Point of dimension 2. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Point2 { - /// First component of the point. - pub x: N, - /// Second component of the point. - pub y: N -} - -point_impl!(Point2, Vector2, Point3, z | x, y); -vectorlike_impl!(Point2, 2, x, y); -from_iterator_impl!(Point2, iterator, iterator); - -/// Point of dimension 3. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Point3 { - /// First component of the point. - pub x: N, - /// Second component of the point. - pub y: N, - /// Third component of the point. - pub z: N -} - -point_impl!(Point3, Vector3, Point4, w | x, y, z); -vectorlike_impl!(Point3, 3, x, y, z); -from_iterator_impl!(Point3, iterator, iterator, iterator); - -/// Point of dimension 4. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Point4 { - /// First component of the point. - pub x: N, - /// Second component of the point. - pub y: N, - /// Third component of the point. - pub z: N, - /// Fourth component of the point. - pub w: N -} - -point_impl!(Point4, Vector4, Point5, a | x, y, z, w); -vectorlike_impl!(Point4, 4, x, y, z, w); -from_iterator_impl!(Point4, iterator, iterator, iterator, iterator); - -/// Point of dimension 5. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Point5 { - /// First component of the point. - pub x: N, - /// Second component of the point. - pub y: N, - /// Third component of the point. - pub z: N, - /// Fourth component of the point. - pub w: N, - /// Fifth of the point. - pub a: N -} - -point_impl!(Point5, Vector5, Point6, b | x, y, z, w, a); -vectorlike_impl!(Point5, 5, x, y, z, w, a); -from_iterator_impl!(Point5, iterator, iterator, iterator, iterator, iterator); - -/// Point of dimension 6. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Point6 { - /// First component of the point. - pub x: N, - /// Second component of the point. - pub y: N, - /// Third component of the point. - pub z: N, - /// Fourth component of the point. - pub w: N, - /// Fifth of the point. - pub a: N, - /// Sixth component of the point. - pub b: N -} - -point_impl!(Point6, Vector6 | x, y, z, w, a, b); -vectorlike_impl!(Point6, 6, x, y, z, w, a, b); -from_iterator_impl!(Point6, iterator, iterator, iterator, iterator, iterator, iterator); diff --git a/src/structs/point_macros.rs b/src/structs/point_macros.rs deleted file mode 100644 index 0fdec197..00000000 --- a/src/structs/point_macros.rs +++ /dev/null @@ -1,202 +0,0 @@ -#![macro_use] - -macro_rules! point_impl( - ($t: ident, $tv: ident | $($compN: ident),+) => ( - - euclidean_space_impl!($t, $tv); - - /* - * - * Origin. - * - */ - impl Origin for $t { - #[inline] - fn origin() -> $t { - $t { - $($compN: ::zero() ),+ - } - } - - #[inline] - fn is_origin(&self) -> bool { - $(self.$compN.is_zero() )&&+ - } - } - - - /* - * - * Point - Point - * - */ - impl> Sub<$t> for $t { - type Output = $tv; - - #[inline] - fn sub(self, right: $t) -> $tv { - *self.as_vector() - *right.as_vector() - } - } - - - /* - * - * Point + Vector - * - */ - impl> Add<$tv> for $t { - type Output = $t; - - #[inline] - fn add(self, right: $tv) -> $t { - $t::new($(self.$compN + right.$compN),+) - } - } - - impl> AddAssign<$tv> for $t { - #[inline] - fn add_assign(&mut self, right: $tv) { - $( self.$compN += right.$compN; )+ - } - } - - - /* - * - * Point - Vector - * - */ - impl> Sub<$tv> for $t { - type Output = $t; - - #[inline] - fn sub(self, right: $tv) -> $t { - $t::new($(self.$compN - right.$compN),+) - } - } - - impl> SubAssign<$tv> for $t { - #[inline] - fn sub_assign(&mut self, right: $tv) { - $( self.$compN -= right.$compN; )+ - } - } - - - - /* - * - * Point as vector. - * - */ - impl $t { - /// Converts this point to its associated vector. - #[inline] - pub fn to_vector(self) -> $tv { - $tv::new( - $(self.$compN),+ - ) - } - - /// Converts a reference to this point to a reference to its associated vector. - #[inline] - pub fn as_vector(&self) -> &$tv { - unsafe { - mem::transmute(self) - } - } - - #[inline] - fn set_coords(&mut self, v: $tv) { - $(self.$compN = v.$compN;)+ - } - } - - impl PointAsVector for $t { - type Vector = $tv; - - #[inline] - fn to_vector(self) -> $tv { - self.to_vector() - } - - #[inline] - fn as_vector(&self) -> &$tv { - self.as_vector() - } - - #[inline] - fn set_coords(&mut self, v: $tv) { - self.set_coords(v) - } - } - - - - /* - * - * NumPoint / FloatPoint - * - */ - impl NumPoint for $t - where N: BaseNum { - } - - impl FloatPoint for $t - where N: BaseFloat + ApproxEq { - } - - - /* - * - * Display - * - */ - impl fmt::Display for $t { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // FIXME: differenciate them from vectors ? - try!(write!(f, "(")); - - let mut it = self.iter(); - - try!(write!(f, "{}", *it.next().unwrap())); - - for comp in it { - try!(write!(f, ", {}", *comp)); - } - - write!(f, ")") - } - } - ); - ($t: ident, $tv: ident, $th: ident, $comp_extra: ident | $($compN: ident),+) => ( - point_impl!($t, $tv | $($compN),+); - - /* - * - * ToHomogeneous / FromHomogeneous - * - */ - impl ToHomogeneous<$th> for $t { - fn to_homogeneous(&self) -> $th { - let mut res: $th = Origin::origin(); - - $( res.$compN = self.$compN; )+ - res.$comp_extra = ::one(); - - res - } - } - - impl + One + Zero> FromHomogeneous<$th> for $t { - fn from(v: &$th) -> $t { - let mut res: $t = Origin::origin(); - - $( res.$compN = v.$compN / v.$comp_extra; )+ - - res - } - } - ) -); diff --git a/src/structs/quaternion.rs b/src/structs/quaternion.rs deleted file mode 100644 index 1656e13c..00000000 --- a/src/structs/quaternion.rs +++ /dev/null @@ -1,684 +0,0 @@ -//! Quaternion definition. - -use std::fmt; -use std::mem; -use std::slice::{Iter, IterMut}; -use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign, Index, IndexMut}; -use std::iter::{FromIterator, IntoIterator}; -use rand::{Rand, Rng}; -use num::{Zero, One}; -use structs::{Vector3, Point3, Rotation3, Matrix3, Unit}; -use traits::operations::{ApproxEq, Inverse, PartialOrder, PartialOrdering, Axpy}; -use traits::structure::{Cast, Indexable, Iterable, IterableMut, Dimension, Shape, BaseFloat, BaseNum, - Bounded, Repeat}; -use traits::geometry::{Norm, Rotation, RotationMatrix, Rotate, RotationTo, Transform}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - - -/// A quaternion. See the `UnitQuaternion` type alias for a quaternion that can be used as a rotation. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Quaternion { - /// The scalar component of the quaternion. - pub w: N, - /// The first vector component of the quaternion. - pub i: N, - /// The second vector component of the quaternion. - pub j: N, - /// The third vector component of the quaternion. - pub k: N -} - -impl Quaternion { - /// The vector part `(i, j, k)` of this quaternion. - #[inline] - pub fn vector(&self) -> &Vector3 { - unsafe { mem::transmute(&self.i) } - } - - /// The scalar part `w` of this quaternion. - #[inline] - pub fn scalar(&self) -> N { - self.w - } -} - -impl> Quaternion { - /// Compute the conjugate of this quaternion. - #[inline] - pub fn conjugate(&self) -> Quaternion { - Quaternion { w: self.w, i: -self.i, j: -self.j, k: -self.k } - } - - /// Replaces this quaternion by its conjugate. - #[inline] - pub fn conjugate_mut(&mut self) { - self.i = -self.i; - self.j = -self.j; - self.k = -self.k; - } -} - -impl Quaternion { - /// Creates a new quaternion from its scalar and vector parts. - pub fn from_parts(scalar: N, vector: Vector3) -> Quaternion { - Quaternion::new(scalar, vector.x, vector.y, vector.z) - } - - /// Creates a new quaternion from its polar decomposition. - /// - /// Note that `axis` is assumed to be a unit vector. - pub fn from_polar_decomposition(scale: N, theta: N, axis: Unit>) - -> Quaternion { - let rot = UnitQuaternion::from_axisangle(axis, theta * ::cast(2.0)); - - rot.unwrap() * scale - } - - /// The polar decomposition of this quaternion. - /// - /// Returns, from left to right: the quaternion norm, the half rotation angle, the rotation - /// axis. If the rotation angle is zero, the rotation axis is set to the `y` axis. - pub fn polar_decomposition(&self) -> (N, N, Unit>) { - let default_axis = Unit::from_unit_value_unchecked(Vector3::y()); - - if let Some((q, n)) = Unit::try_new_and_get(*self, ::zero()) { - if let Some(axis) = Unit::try_new(self.vector(), ::zero()) { - let angle = q.angle() / ::cast(2.0); - - (n, angle, axis) - } - else { - (n, ::zero(), default_axis) - } - } - else { - (::zero(), ::zero(), default_axis) - } - } -} - -impl Inverse for Quaternion { - #[inline] - fn inverse(&self) -> Option> { - let mut res = *self; - - if res.inverse_mut() { - Some(res) - } - else { - None - } - } - - #[inline] - fn inverse_mut(&mut self) -> bool { - let norm_squared = Norm::norm_squared(self); - - if ApproxEq::approx_eq(&norm_squared, &::zero()) { - false - } - else { - self.conjugate_mut(); - *self /= norm_squared; - - true - } - } -} - -impl Norm for Quaternion { - type NormType = N; - - #[inline] - fn norm_squared(&self) -> N { - self.w * self.w + self.i * self.i + self.j * self.j + self.k * self.k - } - - #[inline] - fn normalize(&self) -> Quaternion { - let n = ::norm(self); - *self / n - } - - #[inline] - fn normalize_mut(&mut self) -> N { - let n = ::norm(self); - *self /= n; - - n - } - - #[inline] - fn try_normalize(&self, min_norm: N) -> Option> { - let n = ::norm(self); - - if n <= min_norm { - None - } - else { - Some(*self / n) - } - } - - #[inline] - fn try_normalize_mut(&mut self, min_norm: N) -> Option { - let n = ::norm(self); - - if n <= min_norm { - None - } - else { - *self /= n; - Some(n) - } - } -} - -impl Mul> for Quaternion - where N: Copy + Mul + Sub + Add { - type Output = Quaternion; - - #[inline] - fn mul(self, right: Quaternion) -> Quaternion { - Quaternion::new( - self.w * right.w - self.i * right.i - self.j * right.j - self.k * right.k, - self.w * right.i + self.i * right.w + self.j * right.k - self.k * right.j, - self.w * right.j - self.i * right.k + self.j * right.w + self.k * right.i, - self.w * right.k + self.i * right.j - self.j * right.i + self.k * right.w) - } -} - -impl MulAssign> for Quaternion - where N: Copy + Mul + Sub + Add { - #[inline] - fn mul_assign(&mut self, right: Quaternion) { - *self = *self * right; - } -} - -impl Div> for Quaternion { - type Output = Quaternion; - - #[inline] - fn div(self, right: Quaternion) -> Quaternion { - self * right.inverse().expect("Unable to invert the denominator.") - } -} - -impl DivAssign> for Quaternion { - #[inline] - fn div_assign(&mut self, right: Quaternion) { - *self *= right.inverse().expect("Unable to invert the denominator.") - } -} - -impl Quaternion { - /// Compute the exponential of a quaternion. - #[inline] - pub fn exp(&self) -> Self { - let v = *self.vector(); - let nn = v.norm_squared(); - - if nn.is_zero() { - ::one() - } - else { - let n = nn.sqrt(); - let nv = v / n * n.sin(); - Quaternion::from_parts(n.cos(), nv) * self.scalar().exp() - } - } - - /// Compute the natural logarithm of a quaternion. - #[inline] - pub fn ln(&self) -> Self { - let n = self.norm(); - let v = self.vector(); - let s = self.scalar(); - - Quaternion::from_parts(n.ln(), v.normalize() * (s / n).acos()) - } - - /// Raise the quaternion to a given floating power. - #[inline] - pub fn powf(&self, n: N) -> Self { - (self.ln() * n).exp() - } -} - -impl One for Quaternion where T: Copy + One + Zero + Sub + Add { - #[inline] - fn one() -> Self { - Quaternion::new(T::one(), T::zero(), T::zero(), T::zero()) - } -} - -impl fmt::Display for Quaternion { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Quaternion {} − ({}, {}, {})", self.w, self.i, self.j, self.k) - } -} - -/// A unit quaternions. May be used to represent a rotation. -pub type UnitQuaternion = Unit>; - -impl UnitQuaternion { - /// The underlying quaternion. - /// - /// Same as `self.as_ref()`. - #[inline] - pub fn quaternion(&self) -> &Quaternion { - self.as_ref() - } -} - -impl UnitQuaternion { - /// Creates a new quaternion from a unit vector (the rotation axis) and an angle - /// (the rotation angle). - #[inline] - pub fn from_axisangle(axis: Unit>, angle: N) -> UnitQuaternion { - let (sang, cang) = (angle / ::cast(2.0)).sin_cos(); - - let q = Quaternion::from_parts(cang, axis.unwrap() * sang); - Unit::from_unit_value_unchecked(q) - } - - /// Same as `::from_axisangle` with the axis multiplied with the angle. - #[inline] - pub fn from_scaled_axis(axis: Vector3) -> UnitQuaternion { - let two: N = ::cast(2.0); - let q = Quaternion::from_parts(::zero(), axis / two).exp(); - UnitQuaternion::from_unit_value_unchecked(q) - } - - /// Creates a new unit quaternion from a quaternion. - /// - /// The input quaternion will be normalized. - #[inline] - pub fn from_quaternion(q: &Quaternion) -> UnitQuaternion { - Unit::new(&q) - } - - /// Creates a new unit quaternion from Euler angles. - /// - /// The primitive rotations are applied in order: 1 roll − 2 pitch − 3 yaw. - #[inline] - pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> UnitQuaternion { - let (sr, cr) = (roll * ::cast(0.5)).sin_cos(); - let (sp, cp) = (pitch * ::cast(0.5)).sin_cos(); - let (sy, cy) = (yaw * ::cast(0.5)).sin_cos(); - - let q = Quaternion::new( - cr * cp * cy + sr * sp * sy, - sr * cp * cy - cr * sp * sy, - cr * sp * cy + sr * cp * sy, - cr * cp * sy - sr * sp * cy); - - Unit::from_unit_value_unchecked(q) - } - - /// The rotation angle of this unit quaternion. - #[inline] - pub fn angle(&self) -> N { - self.as_ref().scalar().acos() * ::cast(2.0) - } - - /// The rotation axis of this unit quaternion or `None` if the rotation is zero. - #[inline] - pub fn axis(&self) -> Option>> { - Unit::try_new(self.as_ref().vector(), ::zero()) - } - - /// Builds a rotation matrix from this quaternion. - pub fn to_rotation_matrix(&self) -> Rotation3 { - let ww = self.as_ref().w * self.as_ref().w; - let ii = self.as_ref().i * self.as_ref().i; - let jj = self.as_ref().j * self.as_ref().j; - let kk = self.as_ref().k * self.as_ref().k; - let ij = self.as_ref().i * self.as_ref().j * ::cast(2.0); - let wk = self.as_ref().w * self.as_ref().k * ::cast(2.0); - let wj = self.as_ref().w * self.as_ref().j * ::cast(2.0); - let ik = self.as_ref().i * self.as_ref().k * ::cast(2.0); - let jk = self.as_ref().j * self.as_ref().k * ::cast(2.0); - let wi = self.as_ref().w * self.as_ref().i * ::cast(2.0); - - Rotation3::from_matrix_unchecked( - Matrix3::new( - ww + ii - jj - kk, ij - wk, wj + ik, - wk + ij, ww - ii + jj - kk, jk - wi, - ik - wj, wi + jk, ww - ii - jj + kk - ) - ) - } -} - -impl One for UnitQuaternion { - #[inline] - fn one() -> UnitQuaternion { - let one = Quaternion::new(::one(), ::zero(), ::zero(), ::zero()); - UnitQuaternion::from_unit_value_unchecked(one) - } -} - -impl> Inverse for UnitQuaternion { - #[inline] - fn inverse(&self) -> Option> { - let mut cpy = *self; - - cpy.inverse_mut(); - Some(cpy) - } - - #[inline] - fn inverse_mut(&mut self) -> bool { - *self = Unit::from_unit_value_unchecked(self.as_ref().conjugate()); - - true - } -} - -impl Rand for UnitQuaternion { - #[inline] - fn rand(rng: &mut R) -> UnitQuaternion { - UnitQuaternion::new(&rng.gen()) - } -} - -impl> ApproxEq for UnitQuaternion { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq_eps(&self, other: &UnitQuaternion, eps: &N) -> bool { - ApproxEq::approx_eq_eps(self.as_ref(), other.as_ref(), eps) - } - - #[inline] - fn approx_eq_ulps(&self, other: &UnitQuaternion, ulps: u32) -> bool { - ApproxEq::approx_eq_ulps(self.as_ref(), other.as_ref(), ulps) - } -} - -impl Div> for UnitQuaternion { - type Output = UnitQuaternion; - - #[inline] - fn div(self, other: UnitQuaternion) -> UnitQuaternion { - Unit::from_unit_value_unchecked(self.unwrap() / other.unwrap()) - } -} - -impl DivAssign> for UnitQuaternion { - #[inline] - fn div_assign(&mut self, other: UnitQuaternion) { - *self = Unit::from_unit_value_unchecked(*self.as_ref() / *other.as_ref()) - } -} - -impl Mul> for UnitQuaternion { - type Output = UnitQuaternion; - - #[inline] - fn mul(self, right: UnitQuaternion) -> UnitQuaternion { - Unit::from_unit_value_unchecked(self.unwrap() * right.unwrap()) - } -} - -impl MulAssign> for UnitQuaternion { - #[inline] - fn mul_assign(&mut self, right: UnitQuaternion) { - *self = Unit::from_unit_value_unchecked(*self.as_ref() * *right.as_ref()) - } -} - -impl Mul> for UnitQuaternion { - type Output = Vector3; - - #[inline] - fn mul(self, right: Vector3) -> Vector3 { - let two: N = ::one::() + ::one(); - let t = ::cross(self.as_ref().vector(), &right) * two; - - t * self.as_ref().w + ::cross(self.as_ref().vector(), &t) + right - } -} - -impl Mul> for UnitQuaternion { - type Output = Point3; - - #[inline] - fn mul(self, right: Point3) -> Point3 { - ::origin::>() + self * *right.as_vector() - } -} - -impl> Mul> for Vector3 { - type Output = Vector3; - - #[inline] - fn mul(self, right: UnitQuaternion) -> Vector3 { - let mut inverse_quaternion = right; - - inverse_quaternion.inverse_mut(); - - inverse_quaternion * self - } -} - -impl> Mul> for Point3 { - type Output = Point3; - - #[inline] - fn mul(self, right: UnitQuaternion) -> Point3 { - ::origin::>() + *self.as_vector() * right - } -} - -impl> MulAssign> for Vector3 { - #[inline] - fn mul_assign(&mut self, right: UnitQuaternion) { - *self = *self * right - } -} - -impl> MulAssign> for Point3 { - #[inline] - fn mul_assign(&mut self, right: UnitQuaternion) { - *self = *self * right - } -} - -impl Rotation> for UnitQuaternion { - #[inline] - fn rotation(&self) -> Vector3 { - if let Some(v) = self.axis() { - v.unwrap() * self.angle() - } - else { - ::zero() - } - } - - #[inline] - fn inverse_rotation(&self) -> Vector3 { - -self.rotation() - } - - #[inline] - fn append_rotation_mut(&mut self, amount: &Vector3) { - *self = Rotation::append_rotation(self, amount) - } - - #[inline] - fn append_rotation(&self, amount: &Vector3) -> UnitQuaternion { - *self * UnitQuaternion::from_scaled_axis(*amount) - } - - #[inline] - fn prepend_rotation_mut(&mut self, amount: &Vector3) { - *self = Rotation::prepend_rotation(self, amount) - } - - #[inline] - fn prepend_rotation(&self, amount: &Vector3) -> UnitQuaternion { - UnitQuaternion::from_scaled_axis(*amount) * *self - } - - #[inline] - fn set_rotation(&mut self, v: Vector3) { - *self = UnitQuaternion::from_scaled_axis(v); - } -} - -impl RotationMatrix, Vector3> for UnitQuaternion { - type Output = Rotation3; - - #[inline] - fn to_rotation_matrix(&self) -> Rotation3 { - self.to_rotation_matrix() - } -} - -impl> Rotate> for UnitQuaternion { - #[inline] - fn rotate(&self, v: &Vector3) -> Vector3 { - *self * *v - } - - #[inline] - fn inverse_rotate(&self, v: &Vector3) -> Vector3 { - *v * *self - } -} - -impl> Rotate> for UnitQuaternion { - #[inline] - fn rotate(&self, p: &Point3) -> Point3 { - *self * *p - } - - #[inline] - fn inverse_rotate(&self, p: &Point3) -> Point3 { - *p * *self - } -} - -impl RotationTo for UnitQuaternion { - type AngleType = N; - type DeltaRotationType = UnitQuaternion; - - #[inline] - fn angle_to(&self, other: &Self) -> N { - let delta = self.rotation_to(other); - - delta.as_ref().w.acos() * ::cast(2.0) - } - - #[inline] - fn rotation_to(&self, other: &Self) -> UnitQuaternion { - *other / *self - } -} - -impl> Transform> for UnitQuaternion { - #[inline] - fn transform(&self, v: &Vector3) -> Vector3 { - *self * *v - } - - #[inline] - fn inverse_transform(&self, v: &Vector3) -> Vector3 { - *v * *self - } -} - -impl> Transform> for UnitQuaternion { - #[inline] - fn transform(&self, p: &Point3) -> Point3 { - *self * *p - } - - #[inline] - fn inverse_transform(&self, p: &Point3) -> Point3 { - *p * *self - } -} - -impl fmt::Display for UnitQuaternion { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Unit quaternion {} − ({}, {}, {})", - self.as_ref().w, self.as_ref().i, self.as_ref().j, self.as_ref().k) - } -} - -/* - * - * Dimension - * - */ -impl Dimension for UnitQuaternion { - #[inline] - fn dimension(_: Option>) -> usize { - 3 - } -} - -#[cfg(feature="arbitrary")] -impl Arbitrary for UnitQuaternion { - fn arbitrary(g: &mut G) -> UnitQuaternion { - UnitQuaternion::new(&Arbitrary::arbitrary(g)) - } -} - -impl UnitQuaternion { - /// Compute the exponential of a quaternion. - /// - /// Note that this function yields a `Quaternion` because it looses the unit property. - pub fn exp(&self) -> Quaternion { - self.as_ref().exp() - } - - /// Compute the natural logarithm of a quaternion. - /// - /// Note that this function yields a `Quaternion` because it looses the unit property. The - /// vector part of the return value corresponds to the axis-angle representation (divided by - /// 2.0) of this unit quaternion. - pub fn ln(&self) -> Quaternion { - if let Some(v) = self.axis() { - Quaternion::from_parts(::zero(), v.unwrap() * self.angle()) - } - else { - ::zero() - } - } - - /// Raise this unit quaternion to a given floating power. - /// - /// If this unit quaternion represents a rotation by `theta`, then the resulting quaternion - /// rotates by `n * theta`. - pub fn powf(&self, n: N) -> Self { - if let Some(v) = self.axis() { - UnitQuaternion::from_axisangle(v, self.angle() * n) - } - else { - ::one() - } - } -} - -componentwise_zero!(Quaternion, w, i, j, k); -component_basis_element!(Quaternion, w, i, j, k); -pointwise_add!(Quaternion, w, i, j, k); -pointwise_sub!(Quaternion, w, i, j, k); -from_iterator_impl!(Quaternion, iterator, iterator, iterator, iterator); -vectorlike_impl!(Quaternion, 4, w, i, j, k); diff --git a/src/structs/rotation.rs b/src/structs/rotation.rs deleted file mode 100644 index c5354fba..00000000 --- a/src/structs/rotation.rs +++ /dev/null @@ -1,345 +0,0 @@ -//! Rotations matrices. - -use std::fmt; -use std::ops::{Mul, MulAssign, Index}; -use rand::{Rand, Rng}; -use num::{Zero, One}; -use traits::geometry::{Rotate, Rotation, AbsoluteRotate, RotationMatrix, RotationTo, Transform, - ToHomogeneous, Norm, Cross}; -use traits::structure::{Cast, Dimension, Row, Column, BaseFloat, BaseNum, Eye, Diagonal}; -use traits::operations::{Absolute, Inverse, Transpose, ApproxEq}; -use structs::vector::{Vector1, Vector2, Vector3}; -use structs::point::{Point2, Point3}; -use structs::matrix::{Matrix2, Matrix3, Matrix4}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -#[cfg(feature="abstract_algebra")] -use_special_orthogonal_group_modules!(); - -/// Two dimensional rotation matrix. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Hash, Copy)] -pub struct Rotation2 { - submatrix: Matrix2 -} - -impl Rotation2 { - /// Builds a 2 dimensional rotation matrix from an angle in radian. - pub fn new(angle: Vector1) -> Rotation2 { - let (sia, coa) = angle.x.sin_cos(); - - Rotation2 { - submatrix: Matrix2::new(coa, -sia, sia, coa) - } - } -} - -impl Rotation> for Rotation2 { - #[inline] - fn rotation(&self) -> Vector1 { - Vector1::new((-self.submatrix.m12).atan2(self.submatrix.m11)) - } - - #[inline] - fn inverse_rotation(&self) -> Vector1 { - -self.rotation() - } - - #[inline] - fn append_rotation_mut(&mut self, rotation: &Vector1) { - *self = Rotation::append_rotation(self, rotation) - } - - #[inline] - fn append_rotation(&self, rotation: &Vector1) -> Rotation2 { - Rotation2::new(*rotation) * *self - } - - #[inline] - fn prepend_rotation_mut(&mut self, rotation: &Vector1) { - *self = Rotation::prepend_rotation(self, rotation) - } - - #[inline] - fn prepend_rotation(&self, rotation: &Vector1) -> Rotation2 { - *self * Rotation2::new(*rotation) - } - - #[inline] - fn set_rotation(&mut self, rotation: Vector1) { - *self = Rotation2::new(rotation) - } -} - -impl RotationTo for Rotation2 { - type AngleType = N; - type DeltaRotationType = Rotation2; - - #[inline] - fn angle_to(&self, other: &Self) -> N { - self.rotation_to(other).rotation().norm() - } - - #[inline] - fn rotation_to(&self, other: &Self) -> Rotation2 { - *other * ::inverse(self).unwrap() - } -} - -impl Rand for Rotation2 { - #[inline] - fn rand(rng: &mut R) -> Rotation2 { - Rotation2::new(rng.gen()) - } -} - -impl AbsoluteRotate> for Rotation2 { - #[inline] - fn absolute_rotate(&self, v: &Vector2) -> Vector2 { - // the matrix is skew-symetric, so we dont need to compute the absolute value of every - // component. - let m11 = ::abs(&self.submatrix.m11); - let m12 = ::abs(&self.submatrix.m12); - let m22 = ::abs(&self.submatrix.m22); - - Vector2::new(m11 * v.x + m12 * v.y, m12 * v.x + m22 * v.y) - } -} - - -/* - * 3d rotation - */ -/// Three dimensional rotation matrix. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Hash, Copy)] -pub struct Rotation3 { - submatrix: Matrix3 -} - - -impl Rotation3 { - /// Builds a 3 dimensional rotation matrix from an axis and an angle. - /// - /// # Arguments - /// * `axisangle` - A vector representing the rotation. Its magnitude is the amount of rotation - /// in radian. Its direction is the axis of rotation. - pub fn new(axisangle: Vector3) -> Rotation3 { - if ::is_zero(&Norm::norm_squared(&axisangle)) { - ::one() - } - else { - let mut axis = axisangle; - let angle = axis.normalize_mut(); - let ux = axis.x; - let uy = axis.y; - let uz = axis.z; - let sqx = ux * ux; - let sqy = uy * uy; - let sqz = uz * uz; - let (sin, cos) = angle.sin_cos(); - let one_m_cos = ::one::() - cos; - - Rotation3 { - submatrix: Matrix3::new( - (sqx + (::one::() - sqx) * cos), - (ux * uy * one_m_cos - uz * sin), - (ux * uz * one_m_cos + uy * sin), - - (ux * uy * one_m_cos + uz * sin), - (sqy + (::one::() - sqy) * cos), - (uy * uz * one_m_cos - ux * sin), - - (ux * uz * one_m_cos - uy * sin), - (uy * uz * one_m_cos + ux * sin), - (sqz + (::one::() - sqz) * cos)) - } - } - } - - /// Builds a rotation matrix from an orthogonal matrix. - pub fn from_matrix_unchecked(matrix: Matrix3) -> Rotation3 { - Rotation3 { - submatrix: matrix - } - } - - /// Creates a new rotation from Euler angles. - /// - /// The primitive rotations are applied in order: 1 roll − 2 pitch − 3 yaw. - pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> Rotation3 { - let (sr, cr) = roll.sin_cos(); - let (sp, cp) = pitch.sin_cos(); - let (sy, cy) = yaw.sin_cos(); - - Rotation3::from_matrix_unchecked( - Matrix3::new( - cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr, - sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr, - -sp, cp * sr, cp * cr - ) - ) - } -} - -impl Rotation3 { - /// Creates a rotation that corresponds to the local frame of an observer standing at the - /// origin and looking toward `dir`. - /// - /// It maps the view direction `dir` to the positive `z` axis. - /// - /// # Arguments - /// * dir - The look direction, that is, direction the matrix `z` axis will be aligned with. - /// * up - The vertical direction. The only requirement of this parameter is to not be - /// collinear - /// to `dir`. Non-collinearity is not checked. - #[inline] - pub fn new_observer_frame(dir: &Vector3, up: &Vector3) -> Rotation3 { - let zaxis = Norm::normalize(dir); - let xaxis = Norm::normalize(&Cross::cross(up, &zaxis)); - let yaxis = Norm::normalize(&Cross::cross(&zaxis, &xaxis)); - - Rotation3::from_matrix_unchecked(Matrix3::new( - xaxis.x, yaxis.x, zaxis.x, - xaxis.y, yaxis.y, zaxis.y, - xaxis.z, yaxis.z, zaxis.z)) - } - - - /// Builds a right-handed look-at view matrix without translation. - /// - /// This conforms to the common notion of right handed look-at matrix from the computer - /// graphics community. - /// - /// # Arguments - /// * eye - The eye position. - /// * target - The target position. - /// * up - A vector approximately aligned with required the vertical axis. The only - /// requirement of this parameter is to not be collinear to `target - eye`. - #[inline] - pub fn look_at_rh(dir: &Vector3, up: &Vector3) -> Rotation3 { - Rotation3::new_observer_frame(&(-*dir), up).inverse().unwrap() - } - - /// Builds a left-handed look-at view matrix without translation. - /// - /// This conforms to the common notion of left handed look-at matrix from the computer - /// graphics community. - /// - /// # Arguments - /// * eye - The eye position. - /// * target - The target position. - /// * up - A vector approximately aligned with required the vertical axis. The only - /// requirement of this parameter is to not be collinear to `target - eye`. - #[inline] - pub fn look_at_lh(dir: &Vector3, up: &Vector3) -> Rotation3 { - Rotation3::new_observer_frame(&(*dir), up).inverse().unwrap() - } -} - -impl Rotation> for Rotation3 { - #[inline] - fn rotation(&self) -> Vector3 { - let angle = ((self.submatrix.m11 + self.submatrix.m22 + self.submatrix.m33 - ::one()) / Cast::from(2.0)).acos(); - - if !angle.is_finite() || ::is_zero(&angle) { - // FIXME: handle the non-finite case robustly. - ::zero() - } - else { - let m32_m23 = self.submatrix.m32 - self.submatrix.m23; - let m13_m31 = self.submatrix.m13 - self.submatrix.m31; - let m21_m12 = self.submatrix.m21 - self.submatrix.m12; - - let denom = (m32_m23 * m32_m23 + m13_m31 * m13_m31 + m21_m12 * m21_m12).sqrt(); - - if ::is_zero(&denom) { - // XXX: handle that properly - // panic!("Internal error: singularity.") - ::zero() - } - else { - let a_d = angle / denom; - - Vector3::new(m32_m23 * a_d, m13_m31 * a_d, m21_m12 * a_d) - } - } - } - - #[inline] - fn inverse_rotation(&self) -> Vector3 { - -self.rotation() - } - - - #[inline] - fn append_rotation_mut(&mut self, rotation: &Vector3) { - *self = Rotation::append_rotation(self, rotation) - } - - #[inline] - fn append_rotation(&self, axisangle: &Vector3) -> Rotation3 { - Rotation3::new(*axisangle) * *self - } - - #[inline] - fn prepend_rotation_mut(&mut self, rotation: &Vector3) { - *self = Rotation::prepend_rotation(self, rotation) - } - - #[inline] - fn prepend_rotation(&self, axisangle: &Vector3) -> Rotation3 { - *self * Rotation3::new(*axisangle) - } - - #[inline] - fn set_rotation(&mut self, axisangle: Vector3) { - *self = Rotation3::new(axisangle) - } -} - -impl RotationTo for Rotation3 { - type AngleType = N; - type DeltaRotationType = Rotation3; - - #[inline] - fn angle_to(&self, other: &Self) -> N { - // FIXME: refactor to avoid the normalization of the rotation axisangle vector. - self.rotation_to(other).rotation().norm() - } - - #[inline] - fn rotation_to(&self, other: &Self) -> Rotation3 { - *other * ::inverse(self).unwrap() - } -} - -impl Rand for Rotation3 { - #[inline] - fn rand(rng: &mut R) -> Rotation3 { - Rotation3::new(rng.gen()) - } -} - -impl AbsoluteRotate> for Rotation3 { - #[inline] - fn absolute_rotate(&self, v: &Vector3) -> Vector3 { - Vector3::new( - ::abs(&self.submatrix.m11) * v.x + ::abs(&self.submatrix.m12) * v.y + ::abs(&self.submatrix.m13) * v.z, - ::abs(&self.submatrix.m21) * v.x + ::abs(&self.submatrix.m22) * v.y + ::abs(&self.submatrix.m23) * v.z, - ::abs(&self.submatrix.m31) * v.x + ::abs(&self.submatrix.m32) * v.y + ::abs(&self.submatrix.m33) * v.z) - } -} - - -/* - * Common implementations. - */ - -rotation_impl!(Rotation2, Matrix2, Vector2, Vector1, Point2, Matrix3); -dim_impl!(Rotation2, 2); - -rotation_impl!(Rotation3, Matrix3, Vector3, Vector3, Point3, Matrix4); -dim_impl!(Rotation3, 3); diff --git a/src/structs/rotation_macros.rs b/src/structs/rotation_macros.rs deleted file mode 100644 index 02981503..00000000 --- a/src/structs/rotation_macros.rs +++ /dev/null @@ -1,423 +0,0 @@ -#![macro_use] - -macro_rules! rotation_impl( - ($t: ident, $submatrix: ident, $vector: ident, $rotvector: ident, $point: ident, $homogeneous: ident) => ( - - special_orthogonal_group_impl!($t, $point, $vector); - - impl $t { - /// This rotation's underlying matrix. - #[inline] - pub fn submatrix(&self) -> &$submatrix { - &self.submatrix - } - } - - - /* - * - * Rotate Vector and Point - * - */ - impl Rotate<$vector> for $t { - #[inline] - fn rotate(&self, v: &$vector) -> $vector { - *self * *v - } - - #[inline] - fn inverse_rotate(&self, v: &$vector) -> $vector { - *v * *self - } - } - - impl Rotate<$point> for $t { - #[inline] - fn rotate(&self, p: &$point) -> $point { - *self * *p - } - - #[inline] - fn inverse_rotate(&self, p: &$point) -> $point { - *p * *self - } - } - - - /* - * - * Transform Vector and Point - * - */ - impl Transform<$vector> for $t { - #[inline] - fn transform(&self, v: &$vector) -> $vector { - self.rotate(v) - } - - #[inline] - fn inverse_transform(&self, v: &$vector) -> $vector { - self.inverse_rotate(v) - } - } - - impl Transform<$point> for $t { - #[inline] - fn transform(&self, p: &$point) -> $point { - self.rotate(p) - } - - #[inline] - fn inverse_transform(&self, p: &$point) -> $point { - self.inverse_rotate(p) - } - } - - - - /* - * - * Rotation Matrix - * - */ - impl + BaseFloat> RotationMatrix, $rotvector> for $t { - type Output = $t; - - #[inline] - fn to_rotation_matrix(&self) -> $t { - self.clone() - } - } - - - /* - * - * One - * - */ - impl One for $t { - #[inline] - fn one() -> $t { - $t { submatrix: ::one() } - } - } - - - /* - * - * Eye - * - */ - impl Eye for $t { - #[inline] - fn new_identity(dimension: usize) -> $t { - if dimension != ::dimension::<$t>() { - panic!("Dimension mismatch: should be {}, got {}.", ::dimension::<$t>(), dimension); - } - else { - ::one() - } - } - } - - - /* - * - * Diagonal - * - */ - impl Diagonal<$vector> for $t { - #[inline] - fn from_diagonal(diagonal: &$vector) -> $t { - $t { submatrix: Diagonal::from_diagonal(diagonal) } - } - - #[inline] - fn diagonal(&self) -> $vector { - self.submatrix.diagonal() - } - } - - - /* - * - * Rotation * Rotation - * - */ - impl Mul<$t> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t { submatrix: self.submatrix * right.submatrix } - } - } - - impl MulAssign<$t> for $t { - #[inline] - fn mul_assign(&mut self, right: $t) { - self.submatrix *= right.submatrix - } - } - - - /* - * - * Rotation * Vector - * - */ - impl Mul<$vector> for $t { - type Output = $vector; - - #[inline] - fn mul(self, right: $vector) -> $vector { - self.submatrix * right - } - } - - impl Mul<$t> for $vector { - type Output = $vector; - - #[inline] - fn mul(self, right: $t) -> $vector { - self * right.submatrix - } - } - - impl MulAssign<$t> for $vector { - #[inline] - fn mul_assign(&mut self, right: $t) { - *self *= right.submatrix - } - } - - - /* - * - * Rotation * Point - * - */ - impl Mul<$point> for $t { - type Output = $point; - - #[inline] - fn mul(self, right: $point) -> $point { - self.submatrix * right - } - } - - impl Mul<$t> for $point { - type Output = $point; - - #[inline] - fn mul(self, right: $t) -> $point { - self * right.submatrix - } - } - - impl MulAssign<$t> for $point { - #[inline] - fn mul_assign(&mut self, right: $t) { - *self *= right.submatrix - } - } - - - /* - * - * Inverse - * - */ - impl Inverse for $t { - #[inline] - fn inverse_mut(&mut self) -> bool { - self.transpose_mut(); - - // always succeed - true - } - - #[inline] - fn inverse(&self) -> Option<$t> { - // always succeed - Some(self.transpose()) - } - } - - - /* - * - * Transpose - * - */ - impl Transpose for $t { - #[inline] - fn transpose(&self) -> $t { - $t { submatrix: Transpose::transpose(&self.submatrix) } - } - - #[inline] - fn transpose_mut(&mut self) { - self.submatrix.transpose_mut() - } - } - - - /* - * - * Row - * - */ - impl Row<$vector> for $t { - #[inline] - fn nrows(&self) -> usize { - self.submatrix.nrows() - } - #[inline] - fn row(&self, i: usize) -> $vector { - self.submatrix.row(i) - } - - #[inline] - fn set_row(&mut self, i: usize, row: $vector) { - self.submatrix.set_row(i, row); - } - } - - - /* - * - * Column - * - */ - impl Column<$vector> for $t { - #[inline] - fn ncols(&self) -> usize { - self.submatrix.ncols() - } - #[inline] - fn column(&self, i: usize) -> $vector { - self.submatrix.column(i) - } - - #[inline] - fn set_column(&mut self, i: usize, column: $vector) { - self.submatrix.set_column(i, column); - } - } - - - /* - * - * Index - * - */ - impl Index<(usize, usize)> for $t { - type Output = N; - - fn index(&self, i: (usize, usize)) -> &N { - &self.submatrix[i] - } - } - - - /* - * - * ToHomogeneous - * - */ - impl ToHomogeneous<$homogeneous> for $t { - #[inline] - fn to_homogeneous(&self) -> $homogeneous { - self.submatrix.to_homogeneous() - } - } - - - /* - * - * ApproxEq - * - */ - impl> ApproxEq for $t { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq(&self, other: &$t) -> bool { - ApproxEq::approx_eq(&self.submatrix, &other.submatrix) - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N) -> bool { - ApproxEq::approx_eq_eps(&self.submatrix, &other.submatrix, epsilon) - } - - #[inline] - fn approx_eq_ulps(&self, other: &$t, ulps: u32) -> bool { - ApproxEq::approx_eq_ulps(&self.submatrix, &other.submatrix, ulps) - } - } - - - /* - * - * Absolute - * - */ - impl> Absolute<$submatrix> for $t { - #[inline] - fn abs(m: &$t) -> $submatrix { - Absolute::abs(&m.submatrix) - } - } - - - - /* - * - * Display - * - */ - impl fmt::Display for $t { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let precision = f.precision().unwrap_or(3); - - try!(writeln!(f, "Rotation matrix {{")); - try!(write!(f, "{:.*}", precision, self.submatrix)); - writeln!(f, "}}") - } - } - - - /* - * - * Arbitrary - * - */ - #[cfg(feature="arbitrary")] - impl Arbitrary for $t { - fn arbitrary(g: &mut G) -> $t { - $t::new(Arbitrary::arbitrary(g)) - } - } - ) -); - -macro_rules! dim_impl( - ($t: ident, $dimension: expr) => ( - impl Dimension for $t { - #[inline] - fn dimension(_: Option<$t>) -> usize { - $dimension - } - } - ) -); diff --git a/src/structs/similarity.rs b/src/structs/similarity.rs deleted file mode 100644 index f0dbef03..00000000 --- a/src/structs/similarity.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fmt; -use std::ops::{Mul, Neg, MulAssign}; - -use rand::{Rand, Rng}; -use num::One; -use structs::matrix::{Matrix3, Matrix4}; -use traits::structure::{Dimension, Column, BaseFloat, BaseNum}; -use traits::operations::{Inverse, ApproxEq}; -use traits::geometry::{Transform, Transformation, ToHomogeneous}; - -use structs::vector::{Vector1, Vector2, Vector3}; -use structs::point::{Point2, Point3}; -use structs::rotation::{Rotation2, Rotation3}; -use structs::isometry::{Isometry2, Isometry3}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -// FIXME: the name is not explicit at all but coherent with the other tree-letters names… -/// A two-dimensional similarity transformation. -/// -/// This is a composition of a uniform scale, followed by a rotation, followed by a translation. -/// Vectors `Vector2` are not affected by the translational component of this transformation while -/// points `Point2` are. -/// Similarity transformations conserve angles. Distances are multiplied by some constant (the -/// scale factor). The scale factor cannot be zero. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Similarity2 { - /// The uniform scale applicable by this similarity transformation. - scale: N, - /// The isometry applicable by this similarity transformation. - pub isometry: Isometry2 -} - -/// A three-dimensional similarity transformation. -/// -/// This is a composition of a scale, followed by a rotation, followed by a translation. -/// Vectors `Vector3` are not affected by the translational component of this transformation while -/// points `Point3` are. -/// Similarity transformations conserve angles. Distances are multiplied by some constant (the -/// scale factor). The scale factor cannot be zero. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub struct Similarity3 { - /// The uniform scale applicable by this similarity transformation. - scale: N, - /// The isometry applicable by this similarity transformation. - pub isometry: Isometry3 -} - - -similarity_impl!(Similarity2, Isometry2, Rotation2, Vector2, Vector1, Point2, Matrix3); -dim_impl!(Similarity2, 2); - -similarity_impl!(Similarity3, Isometry3, Rotation3, Vector3, Vector3, Point3, Matrix4); -dim_impl!(Similarity3, 3); diff --git a/src/structs/similarity_macros.rs b/src/structs/similarity_macros.rs deleted file mode 100644 index 85903d97..00000000 --- a/src/structs/similarity_macros.rs +++ /dev/null @@ -1,439 +0,0 @@ -#![macro_use] - -macro_rules! similarity_impl( - ($t: ident, - $isometry: ident, $rotation_matrix: ident, - $vector: ident, $rotvector: ident, - $point: ident, - $homogeneous_matrix: ident) => ( - impl $t { - /* - * - * Constructors. - * - */ - /// Creates a new similarity transformation from a vector, an axis-angle rotation, and a scale factor. - /// - /// The scale factor may be negative but not zero. - #[inline] - pub fn new(translation: $vector, rotation: $rotvector, scale: N) -> $t { - assert!(!scale.is_zero(), "A similarity transformation scale factor cannot be zero."); - - $t { - scale: scale, - isometry: $isometry::new(translation, rotation) - } - } - - /// Creates a new similarity transformation from a rotation matrix, a vector, and a scale factor. - /// - /// The scale factor may be negative but not zero. - #[inline] - pub fn from_rotation_matrix(translation: $vector, rotation: $rotation_matrix, scale: N) -> $t { - assert!(!scale.is_zero(), "A similarity transformation scale factor cannot be zero."); - - $t { - scale: scale, - isometry: $isometry::from_rotation_matrix(translation, rotation) - } - } - - /// Creates a new similarity transformation from an isometry and a scale factor. - /// - /// The scale factor may be negative but not zero. - #[inline] - pub fn from_isometry(isometry: $isometry, scale: N) -> $t { - assert!(!scale.is_zero(), "A similarity transformation scale factor cannot be zero."); - - $t { - scale: scale, - isometry: isometry - } - } - - /* - * - * Methods related to scaling. - * - */ - /// The scale factor of this similarity transformation. - #[inline] - pub fn scale(&self) -> N { - self.scale - } - - /// The inverse scale factor of this similarity transformation. - #[inline] - pub fn inverse_scale(&self) -> N { - ::one::() / self.scale - } - - /// Appends in-place a scale to this similarity transformation. - #[inline] - pub fn append_scale_mut(&mut self, s: &N) { - assert!(!s.is_zero(), "Cannot append a zero scale to a similarity transformation."); - self.scale = *s * self.scale; - self.isometry.translation = self.isometry.translation * *s; - } - - /// Appends a scale to this similarity transformation. - #[inline] - pub fn append_scale(&self, s: &N) -> $t { - assert!(!s.is_zero(), "Cannot append a zero scale to a similarity transformation."); - $t::from_rotation_matrix(self.isometry.translation * *s, self.isometry.rotation, self.scale * *s) - } - - /// Prepends in-place a scale to this similarity transformation. - #[inline] - pub fn prepend_scale_mut(&mut self, s: &N) { - assert!(!s.is_zero(), "Cannot prepend a zero scale to a similarity transformation."); - self.scale = self.scale * *s; - } - - /// Prepends a scale to this similarity transformation. - #[inline] - pub fn prepend_scale(&self, s: &N) -> $t { - assert!(!s.is_zero(), "A similarity transformation scale must not be zero."); - $t::from_isometry(self.isometry, self.scale * *s) - } - - /// Sets the scale of this similarity transformation. - #[inline] - pub fn set_scale(&mut self, s: N) { - assert!(!s.is_zero(), "A similarity transformation scale must not be zero."); - self.scale = s - } - } - - /* - * - * One Impl. - * - */ - impl One for $t { - #[inline] - fn one() -> $t { - $t::from_isometry(::one(), ::one()) - } - } - - - /* - * - * Transformation - * - */ - impl Transformation<$t> for $t { - fn transformation(&self) -> $t { - *self - } - - fn inverse_transformation(&self) -> $t { - // inversion will never fails - Inverse::inverse(self).unwrap() - } - - fn append_transformation_mut(&mut self, t: &$t) { - *self = *t * *self - } - - fn append_transformation(&self, t: &$t) -> $t { - *t * *self - } - - fn prepend_transformation_mut(&mut self, t: &$t) { - *self = *self * *t - } - - fn prepend_transformation(&self, t: &$t) -> $t { - *self * *t - } - - fn set_transformation(&mut self, t: $t) { - *self = t - } - } - - /* - * - * Similarity × Similarity - * - */ - impl Mul<$t> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t::from_rotation_matrix( - self.isometry.translation + self.isometry.rotation * (right.isometry.translation * self.scale), - self.isometry.rotation * right.isometry.rotation, - self.scale * right.scale) - } - } - - impl MulAssign<$t> for $t { - #[inline] - fn mul_assign(&mut self, right: $t) { - self.isometry.translation += self.isometry.rotation * (right.isometry.translation * self.scale); - self.isometry.rotation *= right.isometry.rotation; - self.scale *= right.scale; - } - } - - - /* - * - * Similarity × Isometry - * - */ - impl Mul<$isometry> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $isometry) -> $t { - $t::from_rotation_matrix( - self.isometry.translation + self.isometry.rotation * (right.translation * self.scale), - self.isometry.rotation * right.rotation, - self.scale) - } - } - - impl MulAssign<$isometry> for $t { - #[inline] - fn mul_assign(&mut self, right: $isometry) { - self.isometry.translation += self.isometry.rotation * (right.translation * self.scale); - self.isometry.rotation *= right.rotation; - } - } - - impl Mul<$t> for $isometry { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t::from_rotation_matrix( - self.translation + self.rotation * right.isometry.translation, - self.rotation * right.isometry.rotation, - right.scale) - } - } - - /* - * - * Similarity × Rotation - * - */ - impl Mul<$rotation_matrix> for $t { - type Output = $t; - - #[inline] - fn mul(self, right: $rotation_matrix) -> $t { - $t::from_rotation_matrix( - self.isometry.translation, - self.isometry.rotation * right, - self.scale) - } - } - - impl MulAssign<$rotation_matrix> for $t { - #[inline] - fn mul_assign(&mut self, right: $rotation_matrix) { - self.isometry.rotation *= right; - } - } - - impl Mul<$t> for $rotation_matrix { - type Output = $t; - - #[inline] - fn mul(self, right: $t) -> $t { - $t::from_rotation_matrix( - self * right.isometry.translation, - self * right.isometry.rotation, - right.scale) - } - } - - /* - * - * Similarity × { Point, Vector } - * - */ - impl Mul<$vector> for $t { - type Output = $vector; - - #[inline] - fn mul(self, right: $vector) -> $vector { - self.isometry * (right * self.scale) - } - } - - impl Mul<$point> for $t { - type Output = $point; - - #[inline] - fn mul(self, right: $point) -> $point { - self.isometry * (right * self.scale) - } - } - - // NOTE: there is no viable pre-multiplication definition because of the translation - // component. - - /* - * - * Similarity × Point - * - */ - impl Transform<$point> for $t { - #[inline] - fn transform(&self, p: &$point) -> $point { - self.isometry.transform(&(*p * self.scale)) - } - - #[inline] - fn inverse_transform(&self, p: &$point) -> $point { - self.isometry.inverse_transform(p) / self.scale - } - } - - - /* - * - * Inverse - * - */ - impl> Inverse for $t { - #[inline] - fn inverse_mut(&mut self) -> bool { - self.scale = ::one::() / self.scale; - self.isometry.inverse_mut(); - // We multiply (instead of dividing) by self.scale because it has already been - // inverted. - self.isometry.translation = self.isometry.translation * self.scale; - - // Always succeed. - true - } - - #[inline] - fn inverse(&self) -> Option<$t> { - let mut res = *self; - res.inverse_mut(); - - // Always succeed. - Some(res) - } - } - - - /* - * - * ToHomogeneous - * - */ - impl ToHomogeneous<$homogeneous_matrix> for $t { - fn to_homogeneous(&self) -> $homogeneous_matrix { - let mut res = (*self.isometry.rotation.submatrix() * self.scale).to_homogeneous(); - - // copy the translation - let dimension = Dimension::dimension(None::<$homogeneous_matrix>); - - res.set_column(dimension - 1, self.isometry.translation.as_point().to_homogeneous().to_vector()); - - res - } - } - - - /* - * - * ApproxEq - * - */ - impl> ApproxEq for $t { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, epsilon: &N) -> bool { - ApproxEq::approx_eq_eps(&self.scale, &other.scale, epsilon) && - ApproxEq::approx_eq_eps(&self.isometry, &other.isometry, epsilon) - } - - #[inline] - fn approx_eq_ulps(&self, other: &$t, ulps: u32) -> bool { - ApproxEq::approx_eq_ulps(&self.scale, &other.scale, ulps) && - ApproxEq::approx_eq_ulps(&self.isometry, &other.isometry, ulps) - } - } - - - /* - * - * Rand - * - */ - impl Rand for $t { - #[inline] - fn rand(rng: &mut R) -> $t { - let mut scale: N = rng.gen(); - while scale.is_zero() { - scale = rng.gen(); - } - - $t::from_isometry(rng.gen(), scale) - } - } - - - /* - * - * Arbitrary - * - */ - #[cfg(feature="arbitrary")] - impl Arbitrary for $t { - fn arbitrary(g: &mut G) -> $t { - $t::from_isometry( - Arbitrary::arbitrary(g), - Arbitrary::arbitrary(g) - ) - } - } - - - /* - * - * Display - * - */ - impl fmt::Display for $t { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(writeln!(f, "Similarity transformation {{")); - - if let Some(precision) = f.precision() { - try!(writeln!(f, "... scale factor: {:.*}", precision, self.scale)); - try!(writeln!(f, "... translation: {:.*}", precision, self.isometry.translation)); - try!(writeln!(f, "... rotation matrix:")); - try!(write!(f, "{:.*}", precision, *self.isometry.rotation.submatrix())); - } - else { - try!(writeln!(f, "... scale factor: {}", self.scale)); - try!(writeln!(f, "... translation: {}", self.isometry.translation)); - try!(writeln!(f, "... rotation matrix:")); - try!(write!(f, "{}", *self.isometry.rotation.submatrix())); - } - - writeln!(f, "}}") - } - } - ) -); diff --git a/src/structs/specializations/complex.rs b/src/structs/specializations/complex.rs deleted file mode 100644 index 5be05373..00000000 --- a/src/structs/specializations/complex.rs +++ /dev/null @@ -1,110 +0,0 @@ -/// Implement nalgebra traits for complex numbers from `extra::complex::Cmplex`. - -use std::num::Zero; -use extra::complex::Cmplx; -use traits::operations::{Absolute, Inverse}; -use traits::structure::{Dimension}; - -impl Absolute> for Cmplx { - #[inline] - fn absolute(&self) -> Cmplx { - Cmplx::new(self.re.clone(), self.im.clone()) - } -} - -impl Inverse for Cmplx { - #[inline] - fn inverse(&self) -> Option> { - if self.is_zero() { - None - } - else { - let _1: N = BaseNumCast::from(1.0); - let divisor = _1 / (self.re * self.re - self.im * self.im); - - Some(Cmplx::new(self.re * divisor, -self.im * divisor)) - } - } - - #[inline] - fn inplace_inverse(&mut self) -> bool { - if self.is_zero() { - false - } - else { - let _1: N = BaseNumCast::from(1.0); - let divisor = _1 / (self.re * self.re - self.im * self.im); - - self.re = self.re * divisor; - self.im = -self.im * divisor; - - true - } - } -} - -impl Dimension for Cmplx { - #[inline] - fn dimension(unsused_mut: Option>) -> usize { - 2 - } -} - -impl Rotation> for Cmplx { - #[inline] - fn rotation(&self) -> Vector2 { - } - - #[inline] - fn inverse_rotation(&self) -> Vector2 { - -self.rotation(); - } - - #[inline] - fn rotate_by(&mut self, rotation: &Vector2) { - } - - #[inline] - fn rotated(&self, rotation: &Vector2) -> Cmplx { - } - - #[inline] - fn set_rotation(&mut self, rotation: Vector2) { - } -} - -impl Rotate> for Cmplx { - #[inline] - fn rotate(&self, rotation: &V) -> V { - } - - #[inline] - fn inverse_rotate(&self, rotation: &V) -> V { - } -} - -impl RotationMatrix, Vector2, Rotationmatrix>> for Cmplx { - #[inline] - fn to_rotation_matrix(&self) -> Rotationmatrix> { - } -} - -impl Norm for Cmplx { - #[inline] - fn norm_squared(&self) -> N { - } - - #[inline] - fn normalized(&self) -> Self { - } - - #[inline] - fn normalize(&mut self) -> N { - } -} - -impl AbsoluteRotate { - #[inline] - fn absolute_rotate(&elf, v: &V) -> V { - } -} diff --git a/src/structs/specializations/identity.rs b/src/structs/specializations/identity.rs deleted file mode 100644 index 3e410702..00000000 --- a/src/structs/specializations/identity.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::ops::Mul; -use num::One; -use structs::matrix::Identity; -use traits::operations::{Inverse, Transpose}; -use traits::geometry::{Translate, Rotate, Transform, AbsoluteRotate}; - -impl One for Identity { - #[inline] - fn one() -> Identity { - Identity::new() - } -} - -impl Inverse for Identity { - fn inverse(&self) -> Option { - Some(Identity::new()) - } - - fn inverse_mut(&mut self) -> bool { - true - } -} - -impl Mul for Identity { - type Output = T; - - #[inline] - fn mul(self, other: T) -> T { - other - } -} - -impl Transpose for Identity { - #[inline] - fn transpose(&self) -> Identity { - Identity::new() - } - - #[inline] - fn transpose_mut(&mut self) { - } -} - -impl Translate for Identity { - #[inline] - fn translate(&self, v: &V) -> V { - v.clone() - } - - #[inline] - fn inverse_translate(&self, v: &V) -> V { - v.clone() - } -} - -impl Rotate for Identity { - #[inline] - fn rotate(&self, v: &V) -> V { - v.clone() - } - - #[inline] - fn inverse_rotate(&self, v: &V) -> V { - v.clone() - } -} - -impl AbsoluteRotate for Identity { - #[inline] - fn absolute_rotate(&self, v: &V) -> V { - v.clone() - } -} - -impl Transform for Identity { - #[inline] - fn transform(&self, v: &V) -> V { - v.clone() - } - - #[inline] - fn inverse_transform(&self, v: &V) -> V { - v.clone() - } -} diff --git a/src/structs/specializations/matrix.rs b/src/structs/specializations/matrix.rs deleted file mode 100644 index 27868e7b..00000000 --- a/src/structs/specializations/matrix.rs +++ /dev/null @@ -1,289 +0,0 @@ -use std::ops::{Add, Mul, Neg, MulAssign}; -use structs::vector::{Vector2, Vector3}; -use structs::point::{Point2, Point3}; -use structs::matrix::{Matrix1, Matrix2, Matrix3}; -use traits::operations::{Inverse, Determinant, ApproxEq}; -use traits::structure::BaseNum; - -// some specializations: -impl> Inverse for Matrix1 { - #[inline] - fn inverse(&self) -> Option> { - let mut res = *self; - if res.inverse_mut() { - Some(res) - } - else { - None - } - } - - #[inline] - fn inverse_mut(&mut self) -> bool { - if ApproxEq::approx_eq(&self.m11, &::zero()) { - false - } - else { - self.m11 = ::one::() / Determinant::determinant(self); - - true - } - } -} - -impl + ApproxEq> Inverse for Matrix2 { - #[inline] - fn inverse(&self) -> Option> { - let mut res = *self; - if res.inverse_mut() { - Some(res) - } - else { - None - } - } - - #[inline] - fn inverse_mut(&mut self) -> bool { - let determinant = Determinant::determinant(self); - - if ApproxEq::approx_eq(&determinant, &::zero()) { - false - } - else { - *self = Matrix2::new( - self.m22 / determinant , -self.m12 / determinant, - -self.m21 / determinant, self.m11 / determinant); - - true - } - } -} - -impl + ApproxEq> Inverse for Matrix3 { - #[inline] - fn inverse(&self) -> Option> { - let mut res = *self; - - if res.inverse_mut() { - Some(res) - } - else { - None - } - } - - #[inline] - fn inverse_mut(&mut self) -> bool { - let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23; - let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23; - let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22; - - let determinant = self.m11 * minor_m12_m23 - self.m12 * minor_m11_m23 + self.m13 * minor_m11_m22; - - if ApproxEq::approx_eq(&determinant, &::zero()) { - false - } - else { - *self = Matrix3::new( - (minor_m12_m23 / determinant), - ((self.m13 * self.m32 - self.m33 * self.m12) / determinant), - ((self.m12 * self.m23 - self.m22 * self.m13) / determinant), - - (-minor_m11_m23 / determinant), - ((self.m11 * self.m33 - self.m31 * self.m13) / determinant), - ((self.m13 * self.m21 - self.m23 * self.m11) / determinant), - - (minor_m11_m22 / determinant), - ((self.m12 * self.m31 - self.m32 * self.m11) / determinant), - ((self.m11 * self.m22 - self.m21 * self.m12) / determinant) - ); - - true - } - } -} - -impl Determinant for Matrix1 { - #[inline] - fn determinant(&self) -> N { - self.m11 - } -} - -impl Determinant for Matrix2 { - #[inline] - fn determinant(&self) -> N { - self.m11 * self.m22 - self.m21 * self.m12 - } -} - -impl Determinant for Matrix3 { - #[inline] - fn determinant(&self) -> N { - let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23; - let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23; - let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22; - - self.m11 * minor_m12_m23 - self.m12 * minor_m11_m23 + self.m13 * minor_m11_m22 - } -} - - -impl + Add> Mul> for Matrix3 { - type Output = Matrix3; - - #[inline] - fn mul(self, right: Matrix3) -> Matrix3 { - Matrix3::new( - self.m11 * right.m11 + self.m12 * right.m21 + self.m13 * right.m31, - self.m11 * right.m12 + self.m12 * right.m22 + self.m13 * right.m32, - self.m11 * right.m13 + self.m12 * right.m23 + self.m13 * right.m33, - - self.m21 * right.m11 + self.m22 * right.m21 + self.m23 * right.m31, - self.m21 * right.m12 + self.m22 * right.m22 + self.m23 * right.m32, - self.m21 * right.m13 + self.m22 * right.m23 + self.m23 * right.m33, - - self.m31 * right.m11 + self.m32 * right.m21 + self.m33 * right.m31, - self.m31 * right.m12 + self.m32 * right.m22 + self.m33 * right.m32, - self.m31 * right.m13 + self.m32 * right.m23 + self.m33 * right.m33 - ) - } -} - -impl + Add> Mul> for Matrix2 { - type Output = Matrix2; - - #[inline] - fn mul(self, right: Matrix2) -> Matrix2 { - Matrix2::new( - self.m11 * right.m11 + self.m12 * right.m21, - self.m11 * right.m12 + self.m12 * right.m22, - - self.m21 * right.m11 + self.m22 * right.m21, - self.m21 * right.m12 + self.m22 * right.m22 - ) - } -} - -impl + Add> Mul> for Matrix3 { - type Output = Vector3; - - #[inline] - fn mul(self, right: Vector3) -> Vector3 { - Vector3::new( - self.m11 * right.x + self.m12 * right.y + self.m13 * right.z, - self.m21 * right.x + self.m22 * right.y + self.m23 * right.z, - self.m31 * right.x + self.m32 * right.y + self.m33 * right.z - ) - } -} - -impl + Add> Mul> for Vector3 { - type Output = Vector3; - - #[inline] - fn mul(self, right: Matrix3) -> Vector3 { - Vector3::new( - self.x * right.m11 + self.y * right.m21 + self.z * right.m31, - self.x * right.m12 + self.y * right.m22 + self.z * right.m32, - self.x * right.m13 + self.y * right.m23 + self.z * right.m33 - ) - } -} - -impl + Add> Mul> for Vector2 { - type Output = Vector2; - - #[inline] - fn mul(self, right: Matrix2) -> Vector2 { - Vector2::new( - self.x * right.m11 + self.y * right.m21, - self.x * right.m12 + self.y * right.m22 - ) - } -} - -impl + Add> Mul> for Matrix2 { - type Output = Vector2; - - #[inline] - fn mul(self, right: Vector2) -> Vector2 { - Vector2::new( - self.m11 * right.x + self.m12 * right.y, - self.m21 * right.x + self.m22 * right.y - ) - } -} - -impl + Add> Mul> for Matrix3 { - type Output = Point3; - - #[inline] - fn mul(self, right: Point3) -> Point3 { - Point3::new( - self.m11 * right.x + self.m12 * right.y + self.m13 * right.z, - self.m21 * right.x + self.m22 * right.y + self.m23 * right.z, - self.m31 * right.x + self.m32 * right.y + self.m33 * right.z - ) - } -} - -impl + Add> Mul> for Point3 { - type Output = Point3; - - #[inline] - fn mul(self, right: Matrix3) -> Point3 { - Point3::new( - self.x * right.m11 + self.y * right.m21 + self.z * right.m31, - self.x * right.m12 + self.y * right.m22 + self.z * right.m32, - self.x * right.m13 + self.y * right.m23 + self.z * right.m33 - ) - } -} - -impl + Add> Mul> for Point2 { - type Output = Point2; - - #[inline] - fn mul(self, right: Matrix2) -> Point2 { - Point2::new( - self.x * right.m11 + self.y * right.m21, - self.x * right.m12 + self.y * right.m22 - ) - } -} - -impl + Add> Mul> for Matrix2 { - type Output = Point2; - - #[inline] - fn mul(self, right: Point2) -> Point2 { - Point2::new( - self.m11 * right.x + self.m12 * right.y, - self.m21 * right.x + self.m22 * right.y - ) - } -} - - -macro_rules! impl_mul_assign_from_mul( - ($tleft: ident, $tright: ident) => ( - impl + Add> MulAssign<$tright> for $tleft { - #[inline] - fn mul_assign(&mut self, right: $tright) { - // NOTE: there is probably no interesting optimization compared to the not-inplace - // operation. - *self = *self * right - } - } - ) -); - -impl_mul_assign_from_mul!(Matrix3, Matrix3); -impl_mul_assign_from_mul!(Matrix2, Matrix2); - -impl_mul_assign_from_mul!(Vector3, Matrix3); -impl_mul_assign_from_mul!(Vector2, Matrix2); -impl_mul_assign_from_mul!(Point3, Matrix3); -impl_mul_assign_from_mul!(Point2, Matrix2); diff --git a/src/structs/specializations/primitives.rs b/src/structs/specializations/primitives.rs deleted file mode 100644 index 0605df53..00000000 --- a/src/structs/specializations/primitives.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! nalgebra trait implementation for primitive types. - -use traits::structure::Cast; - -macro_rules! primitive_cast_impl( - ($from: ty, $to: ty) => ( - impl Cast<$from> for $to { - #[inline] - fn from(t: $from) -> $to { - t as $to - } - } - ) -); - -primitive_cast_impl!(f64, f64); -primitive_cast_impl!(f64, f32); -primitive_cast_impl!(f64, i64); -primitive_cast_impl!(f64, i32); -primitive_cast_impl!(f64, i16); -primitive_cast_impl!(f64, i8); -primitive_cast_impl!(f64, u64); -primitive_cast_impl!(f64, u32); -primitive_cast_impl!(f64, u16); -primitive_cast_impl!(f64, u8); -primitive_cast_impl!(f64, isize); -primitive_cast_impl!(f64, usize); - -primitive_cast_impl!(f32, f64); -primitive_cast_impl!(f32, f32); -primitive_cast_impl!(f32, i64); -primitive_cast_impl!(f32, i32); -primitive_cast_impl!(f32, i16); -primitive_cast_impl!(f32, i8); -primitive_cast_impl!(f32, u64); -primitive_cast_impl!(f32, u32); -primitive_cast_impl!(f32, u16); -primitive_cast_impl!(f32, u8); -primitive_cast_impl!(f32, isize); -primitive_cast_impl!(f32, usize); - -primitive_cast_impl!(i64, f64); -primitive_cast_impl!(i64, f32); -primitive_cast_impl!(i64, i64); -primitive_cast_impl!(i64, i32); -primitive_cast_impl!(i64, i16); -primitive_cast_impl!(i64, i8); -primitive_cast_impl!(i64, u64); -primitive_cast_impl!(i64, u32); -primitive_cast_impl!(i64, u16); -primitive_cast_impl!(i64, u8); -primitive_cast_impl!(i64, isize); -primitive_cast_impl!(i64, usize); - -primitive_cast_impl!(i32, f64); -primitive_cast_impl!(i32, f32); -primitive_cast_impl!(i32, i64); -primitive_cast_impl!(i32, i32); -primitive_cast_impl!(i32, i16); -primitive_cast_impl!(i32, i8); -primitive_cast_impl!(i32, u64); -primitive_cast_impl!(i32, u32); -primitive_cast_impl!(i32, u16); -primitive_cast_impl!(i32, u8); -primitive_cast_impl!(i32, isize); -primitive_cast_impl!(i32, usize); - -primitive_cast_impl!(i16, f64); -primitive_cast_impl!(i16, f32); -primitive_cast_impl!(i16, i64); -primitive_cast_impl!(i16, i32); -primitive_cast_impl!(i16, i16); -primitive_cast_impl!(i16, i8); -primitive_cast_impl!(i16, u64); -primitive_cast_impl!(i16, u32); -primitive_cast_impl!(i16, u16); -primitive_cast_impl!(i16, u8); -primitive_cast_impl!(i16, isize); -primitive_cast_impl!(i16, usize); - -primitive_cast_impl!(i8, f64); -primitive_cast_impl!(i8, f32); -primitive_cast_impl!(i8, i64); -primitive_cast_impl!(i8, i32); -primitive_cast_impl!(i8, i16); -primitive_cast_impl!(i8, i8); -primitive_cast_impl!(i8, u64); -primitive_cast_impl!(i8, u32); -primitive_cast_impl!(i8, u16); -primitive_cast_impl!(i8, u8); -primitive_cast_impl!(i8, isize); -primitive_cast_impl!(i8, usize); - -primitive_cast_impl!(u64, f64); -primitive_cast_impl!(u64, f32); -primitive_cast_impl!(u64, i64); -primitive_cast_impl!(u64, i32); -primitive_cast_impl!(u64, i16); -primitive_cast_impl!(u64, i8); -primitive_cast_impl!(u64, u64); -primitive_cast_impl!(u64, u32); -primitive_cast_impl!(u64, u16); -primitive_cast_impl!(u64, u8); -primitive_cast_impl!(u64, isize); -primitive_cast_impl!(u64, usize); - -primitive_cast_impl!(u32, f64); -primitive_cast_impl!(u32, f32); -primitive_cast_impl!(u32, i64); -primitive_cast_impl!(u32, i32); -primitive_cast_impl!(u32, i16); -primitive_cast_impl!(u32, i8); -primitive_cast_impl!(u32, u64); -primitive_cast_impl!(u32, u32); -primitive_cast_impl!(u32, u16); -primitive_cast_impl!(u32, u8); -primitive_cast_impl!(u32, isize); -primitive_cast_impl!(u32, usize); - -primitive_cast_impl!(u16, f64); -primitive_cast_impl!(u16, f32); -primitive_cast_impl!(u16, i64); -primitive_cast_impl!(u16, i32); -primitive_cast_impl!(u16, i16); -primitive_cast_impl!(u16, i8); -primitive_cast_impl!(u16, u64); -primitive_cast_impl!(u16, u32); -primitive_cast_impl!(u16, u16); -primitive_cast_impl!(u16, u8); -primitive_cast_impl!(u16, isize); -primitive_cast_impl!(u16, usize); - -primitive_cast_impl!(u8, f64); -primitive_cast_impl!(u8, f32); -primitive_cast_impl!(u8, i64); -primitive_cast_impl!(u8, i32); -primitive_cast_impl!(u8, i16); -primitive_cast_impl!(u8, i8); -primitive_cast_impl!(u8, u64); -primitive_cast_impl!(u8, u32); -primitive_cast_impl!(u8, u16); -primitive_cast_impl!(u8, u8); -primitive_cast_impl!(u8, isize); -primitive_cast_impl!(u8, usize); - -primitive_cast_impl!(usize, f64); -primitive_cast_impl!(usize, f32); -primitive_cast_impl!(usize, i64); -primitive_cast_impl!(usize, i32); -primitive_cast_impl!(usize, i16); -primitive_cast_impl!(usize, i8); -primitive_cast_impl!(usize, u64); -primitive_cast_impl!(usize, u32); -primitive_cast_impl!(usize, u16); -primitive_cast_impl!(usize, u8); -primitive_cast_impl!(usize, isize); -primitive_cast_impl!(usize, usize); - -primitive_cast_impl!(isize, f64); -primitive_cast_impl!(isize, f32); -primitive_cast_impl!(isize, i64); -primitive_cast_impl!(isize, i32); -primitive_cast_impl!(isize, i16); -primitive_cast_impl!(isize, i8); -primitive_cast_impl!(isize, u64); -primitive_cast_impl!(isize, u32); -primitive_cast_impl!(isize, u16); -primitive_cast_impl!(isize, u8); -primitive_cast_impl!(isize, isize); -primitive_cast_impl!(isize, usize); diff --git a/src/structs/specializations/vector.rs b/src/structs/specializations/vector.rs deleted file mode 100644 index 681c82fe..00000000 --- a/src/structs/specializations/vector.rs +++ /dev/null @@ -1,307 +0,0 @@ -use std::ops::{Sub, Mul, Neg}; -use num::{Zero, One}; -use traits::structure::{Cast, Row, Basis, BaseFloat}; -use traits::geometry::{Norm, Cross, CrossMatrix, RotationTo, UniformSphereSample}; -use structs::vector::{Vector1, Vector2, Vector3, Vector4}; -use structs::matrix::Matrix3; -use structs::rotation::{Rotation2, Rotation3}; - -impl RotationTo for Vector2 { - type AngleType = N; - type DeltaRotationType = Rotation2; - - #[inline] - fn angle_to(&self, other: &Self) -> N { - ::cross(self, other).x.atan2(::dot(self, other)) - } - - #[inline] - fn rotation_to(&self, other: &Self) -> Rotation2 { - Rotation2::new(Vector1::new(self.angle_to(other))) - } -} - -impl RotationTo for Vector3 { - type AngleType = N; - type DeltaRotationType = Rotation3; - - #[inline] - fn angle_to(&self, other: &Self) -> N { - ::cross(self, other).norm().atan2(::dot(self, other)) - } - - #[inline] - fn rotation_to(&self, other: &Self) -> Rotation3 { - let mut axis = ::cross(self, other); - let norm = axis.normalize_mut(); - - if ::is_zero(&norm) { - ::one() - } - else { - let axis_angle = axis * norm.atan2(::dot(self, other)); - - Rotation3::new(axis_angle) - } - } -} - -impl + Sub> Cross for Vector2 { - type CrossProductType = Vector1; - - #[inline] - fn cross(&self, other: &Vector2) -> Vector1 { - Vector1::new(self.x * other.y - self.y * other.x) - } -} - -// FIXME: instead of returning a Vector2, define a Matrix2x1 matrix? -impl + Copy> CrossMatrix> for Vector2 { - #[inline] - fn cross_matrix(&self) -> Vector2 { - Vector2::new(-self.y, self.x) - } -} - -impl + Sub> Cross for Vector3 { - type CrossProductType = Vector3; - - #[inline] - fn cross(&self, other: &Vector3) -> Vector3 { - Vector3::new( - self.y * other.z - self.z * other.y, - self.z * other.x - self.x * other.z, - self.x * other.y - self.y * other.x - ) - } -} - -impl + Zero + Copy> CrossMatrix> for Vector3 { - #[inline] - fn cross_matrix(&self) -> Matrix3 { - Matrix3::new( - ::zero(), -self.z, self.y, - self.z, ::zero(), -self.x, - -self.y, self.x, ::zero() - ) - } -} - -// FIXME: implement this for all other vectors -impl Row> for Vector2 { - #[inline] - fn nrows(&self) -> usize { - 2 - } - - #[inline] - fn row(&self, i: usize) -> Vector1 { - match i { - 0 => Vector1::new(self.x), - 1 => Vector1::new(self.y), - _ => panic!(format!("Index out of range: 2d vectors do not have {} rows. ", i)) - } - } - - #[inline] - fn set_row(&mut self, i: usize, r: Vector1) { - match i { - 0 => self.x = r.x, - 1 => self.y = r.x, - _ => panic!(format!("Index out of range: 2d vectors do not have {} rows.", i)) - - } - } -} - -impl Basis for Vector1 { - #[inline] - fn canonical_basis) -> bool>(mut f: F) { - f(Vector1::new(::one())); - } - - #[inline] - fn orthonormal_subspace_basis) -> bool>(_: &Vector1, _: F) { } - - #[inline] - fn canonical_basis_element(i: usize) -> Option> { - if i == 0 { - Some(Vector1::new(::one())) - } - else { - None - } - } -} - -impl> Basis for Vector2 { - #[inline] - fn canonical_basis) -> bool>(mut f: F) { - if !f(Vector2::new(::one(), ::zero())) { return }; - f(Vector2::new(::zero(), ::one())); - } - - #[inline] - fn orthonormal_subspace_basis) -> bool>(n: &Vector2, mut f: F) { - f(Vector2::new(-n.y, n.x)); - } - - #[inline] - fn canonical_basis_element(i: usize) -> Option> { - if i == 0 { - Some(Vector2::new(::one(), ::zero())) - } - else if i == 1 { - Some(Vector2::new(::zero(), ::one())) - } - else { - None - } - } -} - -impl Basis for Vector3 { - #[inline] - fn canonical_basis) -> bool>(mut f: F) { - if !f(Vector3::new(::one(), ::zero(), ::zero())) { return }; - if !f(Vector3::new(::zero(), ::one(), ::zero())) { return }; - f(Vector3::new(::zero(), ::zero(), ::one())); - } - - #[inline] - fn orthonormal_subspace_basis) -> bool>(n: &Vector3, mut f: F) { - let a = - if n.x.abs() > n.y.abs() { - ::normalize(&Vector3::new(n.z, ::zero(), -n.x)) - } - else { - ::normalize(&Vector3::new(::zero(), -n.z, n.y)) - }; - - if !f(Cross::cross(&a, n)) { return }; - f(a); - } - - #[inline] - fn canonical_basis_element(i: usize) -> Option> { - if i == 0 { - Some(Vector3::new(::one(), ::zero(), ::zero())) - } - else if i == 1 { - Some(Vector3::new(::zero(), ::one(), ::zero())) - } - else if i == 2 { - Some(Vector3::new(::zero(), ::zero(), ::one())) - } - else { - None - } - } -} - -// FIXME: this bad: this fixes definitly the number of samples… -static SAMPLES_2_F64: [Vector2; 21] = [ - Vector2 { x: 1.0, y: 0.0 }, - Vector2 { x: 0.95557281, y: 0.29475517 }, - Vector2 { x: 0.82623877, y: 0.56332006 }, - Vector2 { x: 0.6234898, y: 0.78183148 }, - Vector2 { x: 0.36534102, y: 0.93087375 }, - Vector2 { x: 0.07473009, y: 0.9972038 }, - Vector2 { x: -0.22252093, y: 0.97492791 }, - Vector2 { x: -0.5, y: 0.8660254 }, - Vector2 { x: -0.73305187, y: 0.68017274 }, - Vector2 { x: -0.90096887, y: 0.43388374 }, - Vector2 { x: -0.98883083, y: 0.14904227 }, - Vector2 { x: -0.98883083, y: -0.14904227 }, - Vector2 { x: -0.90096887, y: -0.43388374 }, - Vector2 { x: -0.73305187, y: -0.68017274 }, - Vector2 { x: -0.5, y: -0.8660254 }, - Vector2 { x: -0.22252093, y: -0.97492791 }, - Vector2 { x: 0.07473009, y: -0.9972038 }, - Vector2 { x: 0.36534102, y: -0.93087375 }, - Vector2 { x: 0.6234898, y: -0.78183148 }, - Vector2 { x: 0.82623877, y: -0.56332006 }, - Vector2 { x: 0.95557281, y: -0.29475517 }, -]; - -// Those vectors come from bullet 3d -static SAMPLES_3_F64: [Vector3; 42] = [ - Vector3 { x: 0.000000 , y: -0.000000, z: -1.000000 }, - Vector3 { x: 0.723608 , y: -0.525725, z: -0.447219 }, - Vector3 { x: -0.276388, y: -0.850649, z: -0.447219 }, - Vector3 { x: -0.894426, y: -0.000000, z: -0.447216 }, - Vector3 { x: -0.276388, y: 0.850649 , z: -0.447220 }, - Vector3 { x: 0.723608 , y: 0.525725 , z: -0.447219 }, - Vector3 { x: 0.276388 , y: -0.850649, z: 0.447220 }, - Vector3 { x: -0.723608, y: -0.525725, z: 0.447219 }, - Vector3 { x: -0.723608, y: 0.525725 , z: 0.447219 }, - Vector3 { x: 0.276388 , y: 0.850649 , z: 0.447219 }, - Vector3 { x: 0.894426 , y: 0.000000 , z: 0.447216 }, - Vector3 { x: -0.000000, y: 0.000000 , z: 1.000000 }, - Vector3 { x: 0.425323 , y: -0.309011, z: -0.850654 }, - Vector3 { x: -0.162456, y: -0.499995, z: -0.850654 }, - Vector3 { x: 0.262869 , y: -0.809012, z: -0.525738 }, - Vector3 { x: 0.425323 , y: 0.309011 , z: -0.850654 }, - Vector3 { x: 0.850648 , y: -0.000000, z: -0.525736 }, - Vector3 { x: -0.525730, y: -0.000000, z: -0.850652 }, - Vector3 { x: -0.688190, y: -0.499997, z: -0.525736 }, - Vector3 { x: -0.162456, y: 0.499995 , z: -0.850654 }, - Vector3 { x: -0.688190, y: 0.499997 , z: -0.525736 }, - Vector3 { x: 0.262869 , y: 0.809012 , z: -0.525738 }, - Vector3 { x: 0.951058 , y: 0.309013 , z: 0.000000 }, - Vector3 { x: 0.951058 , y: -0.309013, z: 0.000000 }, - Vector3 { x: 0.587786 , y: -0.809017, z: 0.000000 }, - Vector3 { x: 0.000000 , y: -1.000000, z: 0.000000 }, - Vector3 { x: -0.587786, y: -0.809017, z: 0.000000 }, - Vector3 { x: -0.951058, y: -0.309013, z: -0.000000 }, - Vector3 { x: -0.951058, y: 0.309013 , z: -0.000000 }, - Vector3 { x: -0.587786, y: 0.809017 , z: -0.000000 }, - Vector3 { x: -0.000000, y: 1.000000 , z: -0.000000 }, - Vector3 { x: 0.587786 , y: 0.809017 , z: -0.000000 }, - Vector3 { x: 0.688190 , y: -0.499997, z: 0.525736 }, - Vector3 { x: -0.262869, y: -0.809012, z: 0.525738 }, - Vector3 { x: -0.850648, y: 0.000000 , z: 0.525736 }, - Vector3 { x: -0.262869, y: 0.809012 , z: 0.525738 }, - Vector3 { x: 0.688190 , y: 0.499997 , z: 0.525736 }, - Vector3 { x: 0.525730 , y: 0.000000 , z: 0.850652 }, - Vector3 { x: 0.162456 , y: -0.499995, z: 0.850654 }, - Vector3 { x: -0.425323, y: -0.309011, z: 0.850654 }, - Vector3 { x: -0.425323, y: 0.309011 , z: 0.850654 }, - Vector3 { x: 0.162456 , y: 0.499995 , z: 0.850654 } -]; - -impl UniformSphereSample for Vector1 - where Vector1: One { - #[inline] - fn sample)>(mut f: F) { - f(::one()) - } -} - -impl + Copy> UniformSphereSample for Vector2 { - #[inline] - fn sample)>(mut f: F) { - for sample in SAMPLES_2_F64.iter() { - f(Cast::from(*sample)) - } - } -} - -impl + Copy> UniformSphereSample for Vector3 { - #[inline] - fn sample)>(mut f: F) { - for sample in SAMPLES_3_F64.iter() { - f(Cast::from(*sample)) - } - } -} - -impl + Copy> UniformSphereSample for Vector4 { - #[inline] - fn sample)>(_: F) { - panic!("UniformSphereSample::>::sample : Not yet implemented.") - // for sample in SAMPLES_3_F32.iter() { - // f(Cast::from(*sample)) - // } - } -} diff --git a/src/structs/unit.rs b/src/structs/unit.rs deleted file mode 100644 index d48af8e1..00000000 --- a/src/structs/unit.rs +++ /dev/null @@ -1,80 +0,0 @@ -use traits::geometry::Norm; - - -/// A wrapper that ensures the undelying algebraic entity has a unit norm. -/// -/// Use `.as_ref()` or `.unwrap()` to obtain the undelying value by-reference or by-move. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Unit { - v: T -} - -impl Unit { - /// Normalize the given value and return it wrapped on a `Unit` structure. - #[inline] - pub fn new(v: &T) -> Self { - Unit { v: v.normalize() } - } - - /// Attempts to normalize the given value and return it wrapped on a `Unit` structure. - /// - /// Returns `None` if the norm was smaller or equal to `min_norm`. - #[inline] - pub fn try_new(v: &T, min_norm: T::NormType) -> Option { - v.try_normalize(min_norm).map(|v| Unit { v: v }) - } - - /// Normalize the given value and return it wrapped on a `Unit` structure and its norm. - #[inline] - pub fn new_and_get(mut v: T) -> (Self, T::NormType) { - let n = v.normalize_mut(); - - (Unit { v: v }, n) - } - - /// Normalize the given value and return it wrapped on a `Unit` structure and its norm. - /// - /// Returns `None` if the norm was smaller or equal to `min_norm`. - #[inline] - pub fn try_new_and_get(mut v: T, min_norm: T::NormType) -> Option<(Self, T::NormType)> { - if let Some(n) = v.try_normalize_mut(min_norm) { - Some((Unit { v: v }, n)) - } - else { - None - } - } - - /// Normalizes this value again. This is useful when repeated computations - /// might cause a drift in the norm because of float inaccuracies. - /// - /// Returns the norm before re-normalization (should be close to `1.0`). - #[inline] - pub fn renormalize(&mut self) -> T::NormType { - self.v.normalize_mut() - } -} - -impl Unit { - /// Wraps the given value, assuming it is already normalized. - /// - /// This function is not safe because `v` is not verified to be actually normalized. - #[inline] - pub fn from_unit_value_unchecked(v: T) -> Self { - Unit { v: v } - } - - /// Retrieves the underlying value. - #[inline] - pub fn unwrap(self) -> T { - self.v - } -} - -impl AsRef for Unit { - #[inline] - fn as_ref(&self) -> &T { - &self.v - } -} diff --git a/src/structs/vector.rs b/src/structs/vector.rs deleted file mode 100644 index 86929eb6..00000000 --- a/src/structs/vector.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Vectors with dimension known at compile-time. - -use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign, Index, IndexMut}; -use std::mem; -use std::slice::{Iter, IterMut}; -use std::iter::{Iterator, FromIterator, IntoIterator}; -use std::fmt; -use rand::{Rand, Rng}; -use num::{Zero, One}; -use traits::operations::{ApproxEq, PartialOrder, PartialOrdering, Axpy, Absolute, Mean}; -use traits::geometry::{Transform, Rotate, FromHomogeneous, ToHomogeneous, Dot, Norm, - Translation, Translate}; -use traits::structure::{Basis, Cast, Dimension, Indexable, Iterable, IterableMut, Shape, NumVector, - FloatVector, BaseFloat, BaseNum, Bounded, Repeat}; -use structs::point::{Point1, Point2, Point3, Point4, Point5, Point6}; - -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -#[cfg(feature="abstract_algebra")] -use_vector_space_modules!(); - - -/// Vector of dimension 1. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Vector1 { - /// First component of the vector. - pub x: N -} - -vector_impl!(Vector1, Point1, 1, x); -vectorlike_impl!(Vector1, 1, x); -from_iterator_impl!(Vector1, iterator); -// (specialized); basis_impl!(Vector1, 1); -vec_to_homogeneous_impl!(Vector1, Vector2, y, x); -vec_from_homogeneous_impl!(Vector1, Vector2, y, x); - - - -/// Vector of dimension 2. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Vector2 { - /// First component of the vector. - pub x: N, - /// Second component of the vector. - pub y: N -} - -vector_impl!(Vector2, Point2, 2, x, y); -vectorlike_impl!(Vector2, 2, x, y); -from_iterator_impl!(Vector2, iterator, iterator); -// (specialized); basis_impl!(Vector2, 1); -vec_to_homogeneous_impl!(Vector2, Vector3, z, x, y); -vec_from_homogeneous_impl!(Vector2, Vector3, z, x, y); - - - -/// Vector of dimension 3. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Vector3 { - /// First component of the vector. - pub x: N, - /// Second component of the vector. - pub y: N, - /// Third component of the vector. - pub z: N -} - -vector_impl!(Vector3, Point3, 3, x, y, z); -vectorlike_impl!(Vector3, 3, x, y, z); -from_iterator_impl!(Vector3, iterator, iterator, iterator); -// (specialized); basis_impl!(Vector3, 1); -vec_to_homogeneous_impl!(Vector3, Vector4, w, x, y, z); -vec_from_homogeneous_impl!(Vector3, Vector4, w, x, y, z); - - -/// Vector of dimension 4. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Vector4 { - /// First component of the vector. - pub x: N, - /// Second component of the vector. - pub y: N, - /// Third component of the vector. - pub z: N, - /// Fourth component of the vector. - pub w: N -} - -vector_impl!(Vector4, Point4, 4, x, y, z, w); -vectorlike_impl!(Vector4, 4, x, y, z, w); -from_iterator_impl!(Vector4, iterator, iterator, iterator, iterator); -basis_impl!(Vector4, 4); -vec_to_homogeneous_impl!(Vector4, Vector5, a, x, y, z, w); -vec_from_homogeneous_impl!(Vector4, Vector5, a, x, y, z, w); - - - -/// Vector of dimension 5. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Vector5 { - /// First component of the vector. - pub x: N, - /// Second component of the vector. - pub y: N, - /// Third component of the vector. - pub z: N, - /// Fourth component of the vector. - pub w: N, - /// Fifth of the vector. - pub a: N -} - -vector_impl!(Vector5, Point5, 5, x, y, z, w, a); -vectorlike_impl!(Vector5, 5, x, y, z, w, a); -from_iterator_impl!(Vector5, iterator, iterator, iterator, iterator, iterator); -basis_impl!(Vector5, 5); -vec_to_homogeneous_impl!(Vector5, Vector6, b, x, y, z, w, a); -vec_from_homogeneous_impl!(Vector5, Vector6, b, x, y, z, w, a); - - -/// Vector of dimension 6. -/// -/// The main difference between a point and a vector is that a vector is not affected by -/// translations. -#[repr(C)] -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Debug, Copy)] -pub struct Vector6 { - /// First component of the vector. - pub x: N, - /// Second component of the vector. - pub y: N, - /// Third component of the vector. - pub z: N, - /// Fourth component of the vector. - pub w: N, - /// Fifth of the vector. - pub a: N, - /// Sixth component of the vector. - pub b: N -} - -vector_impl!(Vector6, Point6, 6, x, y, z, w, a, b); -vectorlike_impl!(Vector6, 6, x, y, z, w, a, b); -from_iterator_impl!(Vector6, iterator, iterator, iterator, iterator, iterator, iterator); - -basis_impl!(Vector6, 6); diff --git a/src/structs/vector_macros.rs b/src/structs/vector_macros.rs deleted file mode 100644 index 22987910..00000000 --- a/src/structs/vector_macros.rs +++ /dev/null @@ -1,726 +0,0 @@ -#![macro_use] - -macro_rules! vectorlike_impl( - ($t: ident, $dimension: expr, $($compN: ident),+) => ( - componentwise_neg!($t, $($compN),+); - componentwise_repeat!($t, $($compN),+); - componentwise_arbitrary!($t, $($compN),+); - componentwise_rand!($t, $($compN),+); - pointwise_scalar_add!($t, $($compN),+); - pointwise_scalar_sub!($t, $($compN),+); - pointwise_scalar_mul!($t, $($compN),+); - pointwise_scalar_div!($t, $($compN),+); - component_new!($t, $($compN),+); - partial_order_impl!($t, $($compN),+); - - - /* - * - * Cast between inner scalar type. - * - */ - impl> Cast<$t> for $t { - #[inline] - fn from(v: $t) -> $t { - $t::new($(Cast::from(v.$compN)),+) - } - } - - - /* - * - * ApproxEq - * - */ - impl> ApproxEq for $t { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq(&self, other: &$t) -> bool { - $(ApproxEq::approx_eq(&self.$compN, &other.$compN))&&+ - } - - #[inline] - fn approx_eq_eps(&self, other: &$t, eps: &N) -> bool { - $(ApproxEq::approx_eq_eps(&self.$compN, &other.$compN, eps))&&+ - } - - #[inline] - fn approx_eq_ulps(&self, other: &$t, ulps: u32) -> bool { - $(ApproxEq::approx_eq_ulps(&self.$compN, &other.$compN, ulps))&&+ - } - } - - - /* - * - * Unsafe indexing. - * - */ - impl $t { - /// Unsafe read access to a vector element by index. - #[inline] - pub unsafe fn at_fast(&self, i: usize) -> N { - (*self.as_ref().get_unchecked(i)) - } - - /// Unsafe write access to a vector element by index. - #[inline] - pub unsafe fn set_fast(&mut self, i: usize, val: N) { - (*self.as_mut().get_unchecked_mut(i)) = val - } - } - - - /* - * - * Axpy - * - */ - impl> Axpy for $t { - #[inline] - fn axpy(&mut self, a: &N, x: &$t) { - $( self.$compN.axpy(a, &x.$compN); )+ - } - } - - - /* - * - * Bounded - * - */ - impl Bounded for $t { - #[inline] - fn max_value() -> $t { - $t { - $($compN: Bounded::max_value() ),+ - } - } - - #[inline] - fn min_value() -> $t { - $t { - $($compN: Bounded::min_value() ),+ - } - } - } - - - /* - * - * Container - * - */ - impl $t { - /// The dimension of this entity. - #[inline] - pub fn len(&self) -> usize { - Dimension::dimension(None::<$t>) - } - } - - - /* - * - * Conversions from/to slices - * - */ - impl AsRef<[N; $dimension]> for $t { - #[inline] - fn as_ref(&self) -> &[N; $dimension] { - unsafe { - mem::transmute(self) - } - } - } - - impl AsMut<[N; $dimension]> for $t { - #[inline] - fn as_mut(&mut self) -> &mut [N; $dimension] { - unsafe { - mem::transmute(self) - } - } - } - - impl<'a, N> From<&'a [N; $dimension]> for &'a $t { - #[inline] - fn from(arr: &'a [N; $dimension]) -> &'a $t { - unsafe { - mem::transmute(arr) - } - } - } - - impl<'a, N> From<&'a mut [N; $dimension]> for &'a mut $t { - #[inline] - fn from(arr: &'a mut [N; $dimension]) -> &'a mut $t { - unsafe { - mem::transmute(arr) - } - } - } - - impl<'a, N: Clone> From<&'a [N; $dimension]> for $t { - #[inline] - fn from(arr: &'a [N; $dimension]) -> $t { - let vref: &$t = From::from(arr); - vref.clone() - } - } - - - /* - * - * Dimension - * - */ - impl Dimension for $t { - #[inline] - fn dimension(_: Option<$t>) -> usize { - $dimension - } - } - - - /* - * - * Indexable - * - */ - impl Shape for $t { - #[inline] - fn shape(&self) -> usize { - $dimension - } - } - - impl Indexable for $t { - #[inline] - fn swap(&mut self, i1: usize, i2: usize) { - unsafe { - mem::transmute::<&mut $t, &mut [N; $dimension]>(self).swap(i1, i2) - } - } - - #[inline] - unsafe fn unsafe_at(&self, i: usize) -> N { - (*mem::transmute::<&$t, &[N; $dimension]>(self).get_unchecked(i)) - } - - #[inline] - unsafe fn unsafe_set(&mut self, i: usize, val: N) { - (*mem::transmute::<&mut $t, &mut [N; $dimension]>(self).get_unchecked_mut(i)) = val - } - } - - - /* - * - * Index - * - */ - impl Index for $t where [N]: Index { - type Output = <[N] as Index>::Output; - - fn index(&self, i: T) -> &<[N] as Index>::Output { - &self.as_ref()[i] - } - } - - impl IndexMut for $t where [N]: IndexMut { - fn index_mut(&mut self, i: T) -> &mut <[N] as Index>::Output { - &mut self.as_mut()[i] - } - } - - - /* - * - * Iterable - * - */ - impl Iterable for $t { - #[inline] - fn iter(&self) -> Iter { - unsafe { - mem::transmute::<&$t, &[N; $dimension]>(self).iter() - } - } - } - - impl IterableMut for $t { - #[inline] - fn iter_mut(&mut self) -> IterMut { - unsafe { - mem::transmute::<&mut $t, &mut [N; $dimension]>(self).iter_mut() - } - } - } - ) -); - -macro_rules! vector_impl( - ($t: ident, $tp: ident, $dimension: expr, $($compN: ident),+) => ( - pointwise_add!($t, $($compN),+); - pointwise_sub!($t, $($compN),+); - pointwise_mul!($t, $($compN),+); - pointwise_div!($t, $($compN),+); - componentwise_zero!($t, $($compN),+); - componentwise_one!($t, $($compN),+); - componentwise_absolute!($t, $($compN),+); - component_basis_element!($t, $($compN),+); - vector_space_impl!($t, $dimension, $($compN),+); - - /* - * - * Dot product - * - */ - impl Dot for $t { - #[inline] - fn dot(&self, other: &$t) -> N { - fold_add!($(self.$compN * other.$compN ),+) - } - } - - /* - * - * Norm - * - */ - impl Norm for $t { - type NormType = N; - - #[inline] - fn norm_squared(&self) -> N { - Dot::dot(self, self) - } - - #[inline] - fn normalize(&self) -> $t { - let mut res : $t = *self; - let _ = res.normalize_mut(); - res - } - - #[inline] - fn normalize_mut(&mut self) -> N { - let n = ::norm(self); - *self /= n; - - n - } - - #[inline] - fn try_normalize(&self, min_norm: N) -> Option<$t> { - let n = ::norm(self); - - if n <= min_norm { - None - } - else { - Some(*self / n) - } - } - - #[inline] - fn try_normalize_mut(&mut self, min_norm: N) -> Option { - let n = ::norm(self); - - if n <= min_norm { - None - } - else { - *self /= n; - Some(n) - } - } - } - - - /* - * - * Translation - * - */ - impl + Neg> Translation<$t> for $t { - #[inline] - fn translation(&self) -> $t { - *self - } - - #[inline] - fn inverse_translation(&self) -> $t { - -*self - } - - #[inline] - fn append_translation_mut(&mut self, t: &$t) { - *self = *t + *self; - } - - #[inline] - fn append_translation(&self, t: &$t) -> $t { - *t + *self - } - - #[inline] - fn prepend_translation_mut(&mut self, t: &$t) { - *self = *self + *t; - } - - #[inline] - fn prepend_translation(&self, t: &$t) -> $t { - *self + *t - } - - #[inline] - fn set_translation(&mut self, t: $t) { - *self = t - } - } - - - - /* - * - * Translate - * - */ - impl + Sub> Translate<$tp> for $t { - fn translate(&self, other: &$tp) -> $tp { - *other + *self - } - - fn inverse_translate(&self, other: &$tp) -> $tp { - *other - *self - } - } - - - /* - * - * Rotate - * - */ - impl Rotate for $t { - fn rotate(&self, other: &O) -> O { - *other - } - - fn inverse_rotate(&self, other: &O) -> O { - *other - } - } - - impl Rotate for $tp { - fn rotate(&self, other: &O) -> O { - *other - } - - fn inverse_rotate(&self, other: &O) -> O { - *other - } - } - - - - /* - * - * Transform - * - */ - impl + Sub> Transform<$tp> for $t { - fn transform(&self, other: &$tp) -> $tp { - self.translate(other) - } - - fn inverse_transform(&self, other: &$tp) -> $tp { - self.inverse_translate(other) - } - } - - - - /* - * - * Conversion to point. - * - */ - impl $t { - /// Converts this vector to a point. - #[inline] - pub fn to_point(self) -> $tp { - $tp::new( - $(self.$compN),+ - ) - } - - /// Reinterprets this vector as a point. - #[inline] - pub fn as_point(&self) -> &$tp { - unsafe { - mem::transmute(self) - } - } - } - - - /* - * - * NumVector / FloatVector - * - */ - impl NumVector for $t - where N: BaseNum { - } - - impl FloatVector for $t - where N: BaseFloat { - } - - - - /* - * - * Mean - * - */ - impl> Mean for $t { - #[inline] - fn mean(&self) -> N { - let normalizer = ::cast(1.0f64 / self.len() as f64); - self.iter().fold(::zero(), |acc, x| acc + *x * normalizer) - } - } - - - /* - * - * Display - * - */ - impl fmt::Display for $t { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "(")); - - let mut it = self.iter(); - - let precision = f.precision().unwrap_or(8); - - try!(write!(f, "{:.*}", precision, *it.next().unwrap())); - - for comp in it { - try!(write!(f, ", {:.*}", precision, *comp)); - } - - write!(f, ")") - } - } - ) -); - - -macro_rules! basis_impl( - ($t: ident, $dimension: expr) => ( - impl Basis for $t { - #[inline] - fn canonical_basis) -> bool>(mut f: F) { - for i in 0 .. $dimension { - if !f(Basis::canonical_basis_element(i).unwrap()) { return } - } - } - - #[inline] - fn orthonormal_subspace_basis) -> bool>(n: &$t, mut f: F) { - // Compute the basis of the orthogonal subspace using Gram-Schmidt - // orthogonalization algorithm. - let mut basis: Vec<$t> = Vec::new(); - - for i in 0 .. $dimension { - let mut basis_element : $t = ::zero(); - - unsafe { - basis_element.set_fast(i, ::one()); - } - - if basis.len() == $dimension - 1 { - break; - } - - let mut elt = basis_element; - - elt = elt - *n * Dot::dot(&basis_element, n); - - for v in basis.iter() { - elt = elt - *v * Dot::dot(&elt, v) - }; - - if !ApproxEq::approx_eq(&::norm_squared(&elt), &::zero()) { - let new_element = ::normalize(&elt); - - if !f(new_element) { return }; - - basis.push(new_element); - } - } - } - - #[inline] - fn canonical_basis_element(i: usize) -> Option<$t> { - if i < $dimension { - let mut basis_element : $t = ::zero(); - - unsafe { - basis_element.set_fast(i, ::one()); - } - - Some(basis_element) - } - else { - None - } - } - } - ) -); - - - -macro_rules! from_iterator_impl( - ($t: ident, $param0: ident) => ( - impl FromIterator for $t { - #[inline] - fn from_iter>($param0: I) -> $t { - let mut $param0 = $param0.into_iter(); - $t::new($param0.next().unwrap()) - } - } - ); - ($t: ident, $param0: ident, $($paramN: ident),+) => ( - impl FromIterator for $t { - #[inline] - fn from_iter>($param0: I) -> $t { - let mut $param0 = $param0.into_iter(); - $t::new($param0.next().unwrap(), - $($paramN.next().unwrap()),+) - } - } - ) -); - -macro_rules! vec_to_homogeneous_impl( - ($t: ident, $t2: ident, $extra: ident, $($compN: ident),+) => ( - impl ToHomogeneous<$t2> for $t { - fn to_homogeneous(&self) -> $t2 { - let mut res: $t2 = ::zero(); - - $( res.$compN = self.$compN; )+ - - res - } - } - ) -); - -macro_rules! vec_from_homogeneous_impl( - ($t: ident, $t2: ident, $extra: ident, $($compN: ident),+) => ( - impl + One + Zero> FromHomogeneous<$t2> for $t { - fn from(v: &$t2) -> $t { - let mut res: $t = ::zero(); - - $( res.$compN = v.$compN; )+ - - res - } - } - ) -); - - -// We need to keep this on a separate macro to retrieve the first component nam. -macro_rules! partial_order_impl( - ($t: ident, $comp0: ident $(, $compN: ident)*) => ( - /* - * - * PartialOrder - * - */ - impl PartialOrder for $t { - #[inline] - fn inf(&self, other: &$t) -> $t { - $t::new(self.$comp0.min(other.$comp0), $(self.$compN.min(other.$compN)),*) - } - - #[inline] - fn sup(&self, other: &$t) -> $t { - $t::new(self.$comp0.max(other.$comp0), $(self.$compN.max(other.$compN)),*) - } - - #[inline] - #[allow(unused_mut)] // otherwise there will be a warning for is_eq or Vector1. - fn partial_cmp(&self, other: &$t) -> PartialOrdering { - let is_lt = self.$comp0 < other.$comp0; - let mut is_eq = self.$comp0 == other.$comp0; - - if is_lt { // < - $( - if self.$compN > other.$compN { - return PartialOrdering::NotComparable - } - )* - - PartialOrdering::PartialLess - } - else { // >= - $( - if self.$compN < other.$compN { - return PartialOrdering::NotComparable - } - else if self.$compN > other.$compN { - is_eq = false; - } - - )* - - if is_eq { - PartialOrdering::PartialEqual - } - else { - PartialOrdering::PartialGreater - } - } - } - - #[inline] - fn partial_lt(&self, other: &$t) -> bool { - self.$comp0 < other.$comp0 $(&& self.$compN < other.$compN)* - } - - #[inline] - fn partial_le(&self, other: &$t) -> bool { - self.$comp0 <= other.$comp0 $(&& self.$compN <= other.$compN)* - } - - #[inline] - fn partial_gt(&self, other: &$t) -> bool { - self.$comp0 > other.$comp0 $(&& self.$compN > other.$compN)* - } - - #[inline] - fn partial_ge(&self, other: &$t) -> bool { - self.$comp0 >= other.$comp0 $(&& self.$compN >= other.$compN)* - } - } - ) -); diff --git a/src/structs/vectorn.rs b/src/structs/vectorn.rs deleted file mode 100644 index c5433dce..00000000 --- a/src/structs/vectorn.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::slice::{Iter, IterMut}; -use std::iter::{FromIterator, IntoIterator}; -use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign, Index, IndexMut}; -use std::mem; -use rand::{Rand, Rng}; -use num::{Zero, One}; -use generic_array::{GenericArray, ArrayLength}; -use traits::operations::{ApproxEq, Axpy, Mean}; -use traits::geometry::{Dot, Norm}; -use traits::structure::{Iterable, IterableMut, Indexable, Shape, BaseFloat, BaseNum, Cast, Dimension}; -#[cfg(feature="arbitrary")] -use quickcheck::{Arbitrary, Gen}; - -/// A stack-allocated vector of arbitrary dimension. -#[repr(C)] -#[derive(Eq, PartialEq, Debug)] // FIXME: Hash, RustcEncodable, RustcDecodable -pub struct VectorN> { - /// The underlying data of the vector. - pub at: GenericArray -} - -unsafe impl> Send for VectorN { -} - -impl> Clone for VectorN { - fn clone(&self) -> VectorN { - VectorN::new(self.at.clone()) - } -} - -impl> Copy for VectorN - where D::ArrayType: Copy { } - -impl> VectorN { - /// Creates a new vector from a given arbirtarily-sized array. - #[inline] - pub fn new(components: GenericArray) -> VectorN { - VectorN { - at: components - } - } - - /// The vector length. - #[inline] - pub fn len(&self) -> usize { - self.at.len() - } -} - -impl> Dimension for VectorN { - fn dimension(_unused: Option) -> usize { - D::to_usize() - } -} - -impl> FromIterator for VectorN { - #[inline] - fn from_iter>(param: I) -> VectorN { - let mut res: VectorN = unsafe { mem::uninitialized() }; - - let mut it = param.into_iter(); - - for e in res.iter_mut() { - *e = it.next().expect("Not enough data into the provided iterator to initialize this `VectorN`."); - } - - res - } -} - -impl> Rand for VectorN { - #[inline] - fn rand(rng: &mut R) -> VectorN { - let mut res: VectorN = unsafe { mem::uninitialized() }; - - for e in res.iter_mut() { - *e = Rand::rand(rng) - } - - res - } -} - -impl> One for VectorN { - #[inline] - fn one() -> VectorN { - let mut res: VectorN = unsafe { mem::uninitialized() }; - - for e in res.iter_mut() { - *e = ::one() - } - - res - } -} - -impl> Zero for VectorN { - #[inline] - fn zero() -> VectorN { - let mut res: VectorN = unsafe { mem::uninitialized() }; - - for e in res.iter_mut() { - *e = ::zero() - } - - res - } - - #[inline] - fn is_zero(&self) -> bool { - self.iter().all(|e| e.is_zero()) - } -} - -#[cfg(feature="arbitrary")] -impl> Arbitrary for VectorN { - #[inline] - fn arbitrary(g: &mut G) -> VectorN { - (0 .. D::to_usize()).map(|_| Arbitrary::arbitrary(g)).collect() - } -} - - -vecn_dvec_common_impl!(VectorN, D); diff --git a/src/structs/vectorn_macros.rs b/src/structs/vectorn_macros.rs deleted file mode 100644 index 05046c1a..00000000 --- a/src/structs/vectorn_macros.rs +++ /dev/null @@ -1,607 +0,0 @@ -#![macro_use] - -macro_rules! vecn_dvec_common_impl( - ($vecn: ident $(, $param: ident)*) => ( - /* - * - * Zero. - * - */ - impl)*> $vecn { - /// Tests if all components of the vector are zeroes. - #[inline] - pub fn is_zero(&self) -> bool { - self.as_ref().iter().all(|e| e.is_zero()) - } - } - - /* - * - * AsRef/AsMut - * - */ - impl)*> AsRef<[N]> for $vecn { - #[inline] - fn as_ref(&self) -> &[N] { - &self.at[.. self.len()] - } - } - - impl)*> AsMut<[N]> for $vecn { - #[inline] - fn as_mut(&mut self) -> &mut [N] { - let len = self.len(); - &mut self.at[.. len] - } - } - - /* - * - * Shape. - * - */ - impl)*> Shape for $vecn { - #[inline] - fn shape(&self) -> usize { - self.len() - } - } - - /* - * - * Index et. al. - * - */ - impl)*> Indexable for $vecn { - #[inline] - fn swap(&mut self, i: usize, j: usize) { - assert!(i < self.len()); - assert!(j < self.len()); - self.as_mut().swap(i, j); - } - - #[inline] - unsafe fn unsafe_at(&self, i: usize) -> N { - *self[..].get_unchecked(i) - } - - #[inline] - unsafe fn unsafe_set(&mut self, i: usize, val: N) { - *self[..].get_unchecked_mut(i) = val - } - - } - - impl)*> Index for $vecn where [N]: Index { - type Output = <[N] as Index>::Output; - - fn index(&self, i: T) -> &<[N] as Index>::Output { - &self.as_ref()[i] - } - } - - impl)*> IndexMut for $vecn where [N]: IndexMut { - fn index_mut(&mut self, i: T) -> &mut <[N] as Index>::Output { - &mut self.as_mut()[i] - } - } - - /* - * - * Iterable et al. - * - */ - impl)*> Iterable for $vecn { - #[inline] - fn iter(&self) -> Iter { - self.as_ref().iter() - } - } - - impl)*> IterableMut for $vecn { - #[inline] - fn iter_mut(&mut self) -> IterMut { - self.as_mut().iter_mut() - } - } - - /* - * - * Axpy - * - */ - impl + Mul $(, $param : ArrayLength)*> - Axpy for $vecn { - fn axpy(&mut self, a: &N, x: &$vecn) { - assert!(self.len() == x.len()); - - for i in 0 .. x.len() { - unsafe { - let self_i = self.unsafe_at(i); - self.unsafe_set(i, self_i + *a * x.unsafe_at(i)) - } - } - } - } - - /* - * - * Mul - * - */ - impl + Zero $(, $param : ArrayLength)*> - Mul<$vecn> for $vecn { - type Output = $vecn; - - #[inline] - fn mul(self, right: $vecn) -> $vecn { - assert!(self.len() == right.len()); - - let mut res = self; - - for (left, right) in res.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left = *left * *right - } - - res - } - } - - impl + Zero $(, $param : ArrayLength)*> - Mul for $vecn { - type Output = $vecn; - - #[inline] - fn mul(self, right: N) -> $vecn { - let mut res = self; - - for e in res.as_mut().iter_mut() { - *e = *e * right - } - - res - } - } - - impl)*> MulAssign<$vecn> for $vecn - where N: Copy + MulAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn mul_assign(&mut self, right: $vecn) { - assert!(self.len() == right.len()); - - for (left, right) in self.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left *= *right - } - } - } - - impl)*> MulAssign for $vecn - where N: Copy + MulAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn mul_assign(&mut self, right: N) { - for e in self.as_mut().iter_mut() { - *e *= right - } - } - } - - impl<$($param : ArrayLength),*> Mul<$vecn> for f32 { - type Output = $vecn; - - #[inline] - fn mul(self, right: $vecn) -> $vecn { - let mut res = right; - - for e in res.as_mut().iter_mut() { - *e = self * *e; - } - - res - } - } - - impl<$($param : ArrayLength),*> Mul<$vecn> for f64 { - type Output = $vecn; - - #[inline] - fn mul(self, right: $vecn) -> $vecn { - let mut res = right; - - for e in res.as_mut().iter_mut() { - *e = self * *e; - } - - res - } - } - - /* - * - * Div. - * - */ - impl + Zero $(, $param : ArrayLength)*> - Div<$vecn> for $vecn { - type Output = $vecn; - - #[inline] - fn div(self, right: $vecn) -> $vecn { - assert!(self.len() == right.len()); - - let mut res = self; - - for (left, right) in res.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left = *left / *right - } - - res - } - } - - impl + Zero $(, $param : ArrayLength)*> Div for $vecn { - type Output = $vecn; - - #[inline] - fn div(self, right: N) -> $vecn { - let mut res = self; - - for e in res.as_mut().iter_mut() { - *e = *e / right - } - - res - } - } - - impl<'a, N: Copy + Div + Zero $(, $param : ArrayLength)*> Div<$vecn> for &'a $vecn { - type Output = $vecn; - - #[inline] - fn div(self, right: $vecn) -> $vecn { - self.clone() / right - } - } - - impl<'a, N: Copy + Div + Zero $(, $param : ArrayLength)*> Div for &'a $vecn { - type Output = $vecn; - - #[inline] - fn div(self, right: N) -> $vecn { - self.clone() / right - } - } - - impl)*> DivAssign<$vecn> for $vecn - where N: Copy + DivAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn div_assign(&mut self, right: $vecn) { - assert!(self.len() == right.len()); - - for (left, right) in self.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left /= *right - } - } - } - - impl)*> DivAssign for $vecn - where N: Copy + DivAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn div_assign(&mut self, right: N) { - for e in self.as_mut().iter_mut() { - *e /= right - } - } - } - - /* - * - * Add. - * - */ - impl + Zero $(, $param : ArrayLength)*> - Add<$vecn> for $vecn { - type Output = $vecn; - - #[inline] - fn add(self, right: $vecn) -> $vecn { - assert!(self.len() == right.len()); - - let mut res = self; - - for (left, right) in res.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left = *left + *right - } - - res - } - } - - impl + Zero $(, $param : ArrayLength)*> Add for $vecn { - type Output = $vecn; - - #[inline] - fn add(self, right: N) -> $vecn { - let mut res = self; - - for e in res.as_mut().iter_mut() { - *e = *e + right - } - - res - } - } - - impl)*> AddAssign<$vecn> for $vecn - where N: Copy + AddAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn add_assign(&mut self, right: $vecn) { - assert!(self.len() == right.len()); - - for (left, right) in self.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left += *right - } - } - } - - impl)*> AddAssign for $vecn - where N: Copy + AddAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn add_assign(&mut self, right: N) { - for e in self.as_mut().iter_mut() { - *e += right - } - } - } - - impl<$($param : ArrayLength),*> Add<$vecn> for f32 { - type Output = $vecn; - - #[inline] - fn add(self, right: $vecn) -> $vecn { - let mut res = right; - - for e in res.as_mut().iter_mut() { - *e = self + *e; - } - - res - } - } - - impl<$($param : ArrayLength),*> Add<$vecn> for f64 { - type Output = $vecn; - - #[inline] - fn add(self, right: $vecn) -> $vecn { - let mut res = right; - - for e in res.as_mut().iter_mut() { - *e = self + *e; - } - - res - } - } - - /* - * - * Sub. - * - */ - impl + Zero $(, $param : ArrayLength)*> - Sub<$vecn> for $vecn { - type Output = $vecn; - - #[inline] - fn sub(self, right: $vecn) -> $vecn { - assert!(self.len() == right.len()); - - let mut res = self; - - for (left, right) in res.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left = *left - *right - } - - res - } - } - - impl + Zero $(, $param : ArrayLength)*> Sub for $vecn { - type Output = $vecn; - - #[inline] - fn sub(self, right: N) -> $vecn { - let mut res = self; - - for e in res.as_mut().iter_mut() { - *e = *e - right - } - - res - } - } - - impl)*> SubAssign<$vecn> for $vecn - where N: Copy + SubAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn sub_assign(&mut self, right: $vecn) { - assert!(self.len() == right.len()); - - for (left, right) in self.as_mut().iter_mut().zip(right.as_ref().iter()) { - *left -= *right - } - } - } - - impl)*> SubAssign for $vecn - where N: Copy + SubAssign + Zero $(, $param : ArrayLength)* { - #[inline] - fn sub_assign(&mut self, right: N) { - for e in self.as_mut().iter_mut() { - *e -= right - } - } - } - - impl<$($param : ArrayLength),*> Sub<$vecn> for f32 { - type Output = $vecn; - - #[inline] - fn sub(self, right: $vecn) -> $vecn { - let mut res = right; - - for e in res.as_mut().iter_mut() { - *e = self - *e; - } - - res - } - } - - impl<$($param : ArrayLength),*> Sub<$vecn> for f64 { - type Output = $vecn; - - #[inline] - fn sub(self, right: $vecn) -> $vecn { - let mut res = right; - - for e in res.as_mut().iter_mut() { - *e = self - *e; - } - - res - } - } - - /* - * - * Neg. - * - */ - impl + Zero + Copy $(, $param : ArrayLength)*> Neg for $vecn { - type Output = $vecn; - - #[inline] - fn neg(mut self) -> $vecn { - for e in self.as_mut().iter_mut() { - *e = -*e; - } - - self - } - } - - /* - * - * Dot. - * - */ - impl)*> Dot for $vecn { - #[inline] - fn dot(&self, other: &$vecn) -> N { - assert!(self.len() == other.len()); - let mut res: N = ::zero(); - for i in 0 .. self.len() { - res = res + unsafe { self.unsafe_at(i) * other.unsafe_at(i) }; - } - res - } - } - - /* - * - * Norm. - * - */ - impl)*> Norm for $vecn { - type NormType = N; - - #[inline] - fn norm_squared(&self) -> N { - ::dot(self, self) - } - - #[inline] - fn normalize(&self) -> $vecn { - let mut res : $vecn = self.clone(); - let _ = res.normalize_mut(); - res - } - - #[inline] - fn normalize_mut(&mut self) -> N { - let n = ::norm(self); - *self /= n; - - n - } - - #[inline] - fn try_normalize(&self, min_norm: N) -> Option<$vecn> { - let n = ::norm(self); - - if n <= min_norm { - None - } - else { - Some(self / n) - } - } - - #[inline] - fn try_normalize_mut(&mut self, min_norm: N) -> Option { - let n = ::norm(self); - - if n <= min_norm { - None - } - else { - *self /= n; - Some(n) - } - } - } - - /* - * - * Mean. - * - */ - impl $(, $param : ArrayLength)*> Mean for $vecn { - #[inline] - fn mean(&self) -> N { - let normalizer = ::cast(1.0f64 / self.len() as f64); - self.iter().fold(::zero(), |acc, x| acc + *x * normalizer) - } - } - - /* - * - * ApproxEq - * - */ - impl $(, $param : ArrayLength)*> ApproxEq for $vecn { - #[inline] - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - #[inline] - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - #[inline] - fn approx_eq_eps(&self, other: &$vecn, epsilon: &N) -> bool { - let mut zip = self.as_ref().iter().zip(other.as_ref().iter()); - zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon)) - } - - #[inline] - fn approx_eq_ulps(&self, other: &$vecn, ulps: u32) -> bool { - let mut zip = self.as_ref().iter().zip(other.as_ref().iter()); - zip.all(|(a, b)| ApproxEq::approx_eq_ulps(a, b, ulps)) - } - } - ) -); diff --git a/src/traits/axpy.rs b/src/traits/axpy.rs new file mode 100644 index 00000000..20fa7f3a --- /dev/null +++ b/src/traits/axpy.rs @@ -0,0 +1,38 @@ +use alga::general::Field; + +use core::{Scalar, Matrix}; +use core::dimension::{Dim, DimName, U1}; +use core::storage::StorageMut; + +use geometry::PointBase; + +/// Operation that combines scalar multiplication and vector addition. +pub trait Axpy { + /// Computes `self = a * x + self`. + fn axpy(&mut self, a: A, x: &Self); +} + +impl Axpy for Matrix +where N: Scalar + Field, + S: StorageMut { + #[inline] + fn axpy(&mut self, a: N, x: &Self) { + for (me, x) in self.iter_mut().zip(x.iter()) { + *me += *x * a; + } + } +} + + +impl Axpy for PointBase +where N: Scalar + Field, + S: StorageMut { + #[inline] + fn axpy(&mut self, a: N, x: &Self) { + for (me, x) in self.coords.iter_mut().zip(x.coords.iter()) { + *me += *x * a; + } + } +} + +// FIXME: implemente Axpy with matrices and transforms. diff --git a/src/traits/geometry.rs b/src/traits/geometry.rs deleted file mode 100644 index 559160c5..00000000 --- a/src/traits/geometry.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! Traits of operations having a well-known or explicit geometric meaning. - -use std::ops::{Neg, Mul}; -use num::Float; -use traits::structure::{BaseFloat, SquareMatrix}; - -/// Trait of object which represent a translation, and to wich new translation -/// can be appended. -pub trait Translation { - // FIXME: add a "from translation: translantion(V) -> Self ? - /// Gets the translation associated with this object. - fn translation(&self) -> V; - - /// Gets the inverse translation associated with this object. - fn inverse_translation(&self) -> V; - - /// Appends a translation to this object. - fn append_translation_mut(&mut self, &V); - - /// Appends the translation `amount` to a copy of `t`. - fn append_translation(&self, amount: &V) -> Self; - - /// Prepends a translation to this object. - fn prepend_translation_mut(&mut self, &V); - - /// Prepends the translation `amount` to a copy of `t`. - fn prepend_translation(&self, amount: &V) -> Self; - - /// Sets the translation. - fn set_translation(&mut self, V); -} - -/// Trait of objects able to translate other objects. This is typically -/// implemented by vectors to translate points. -pub trait Translate { - /// Apply a translation to an object. - fn translate(&self, &V) -> V; - - /// Apply an inverse translation to an object. - fn inverse_translate(&self, &V) -> V; -} - -/// Trait of object which can represent a rotation, and to which new rotations can be appended. A -/// rotation is assumed to be an isometry without translation and without reflexion. -pub trait Rotation { - /// Gets the rotation associated with `self`. - fn rotation(&self) -> V; - - /// Gets the inverse rotation associated with `self`. - fn inverse_rotation(&self) -> V; - - /// Appends a rotation to this object. - fn append_rotation_mut(&mut self, &V); - - /// Appends the rotation `amount` to a copy of `t`. - fn append_rotation(&self, amount: &V) -> Self; - - /// Prepends a rotation to this object. - fn prepend_rotation_mut(&mut self, &V); - - /// Prepends the rotation `amount` to a copy of `t`. - fn prepend_rotation(&self, amount: &V) -> Self; - - /// Sets the rotation of `self`. - fn set_rotation(&mut self, V); -} - -/// Trait of object that can be rotated to be superimposed with another one of the same nature. -pub trait RotationTo { - /// Type of the angle between two elements. - type AngleType; - - /// Type of the rotation between two elements. - type DeltaRotationType; - - /// Computes an angle nedded to transform the first element to the second one using a - /// rotation. - fn angle_to(&self, other: &Self) -> Self::AngleType; - - /// Computes the smallest rotation needed to transform the first element to the second one. - fn rotation_to(&self, other: &Self) -> Self::DeltaRotationType; -} - -/// Trait of objects able to rotate other objects. -/// -/// This is typically implemented by matrices which rotate vectors. -pub trait Rotate { - /// Applies a rotation to `v`. - fn rotate(&self, v: &V) -> V; - - /// Applies an inverse rotation to `v`. - fn inverse_rotate(&self, v: &V) -> V; -} - -/// Various composition of rotation and translation. -/// -/// Utilities to make rotations with regard to a point different than the origin. All those -/// operations are the composition of rotations and translations. -/// -/// Those operations are automatically implemented in term of the `Rotation` and `Translation` -/// traits. -pub trait RotationWithTranslation + Copy, AV>: Rotation + Translation + Sized { - /// Applies a rotation centered on a specific point. - /// - /// # Arguments - /// * `t` - the object to be rotated. - /// * `amount` - the rotation to apply. - /// * `point` - the center of rotation. - #[inline] - fn append_rotation_wrt_point(&self, amount: &AV, center: &LV) -> Self { - let mut res = Translation::append_translation(self, &-*center); - - res.append_rotation_mut(amount); - res.append_translation_mut(center); - - res - } - - /// Rotates `self` using a specific center of rotation. - /// - /// The rotation is applied in-place. - /// - /// # Arguments - /// * `amount` - the rotation to be applied - /// * `center` - the new center of rotation - #[inline] - fn append_rotation_wrt_point_mut(&mut self, amount: &AV, center: &LV) { - self.append_translation_mut(&-*center); - self.append_rotation_mut(amount); - self.append_translation_mut(center); - } - - /// Applies a rotation centered on the translation of `m`. - /// - /// # Arguments - /// * `t` - the object to be rotated. - /// * `amount` - the rotation to apply. - #[inline] - fn append_rotation_wrt_center(&self, amount: &AV) -> Self { - RotationWithTranslation::append_rotation_wrt_point(self, amount, &self.translation()) - } - - /// Applies a rotation centered on the translation of `m`. - /// - /// The rotation os applied on-place. - /// - /// # Arguments - /// * `amount` - the rotation to apply. - #[inline] - fn append_rotation_wrt_center_mut(&mut self, amount: &AV) { - let center = self.translation(); - self.append_rotation_wrt_point_mut(amount, ¢er) - } -} - -impl + Copy, AV, M: Rotation + Translation> RotationWithTranslation for M { -} - -/// Trait of transformation having a rotation extractable as a rotation matrix. This can typically -/// be implemented by quaternions to convert them to a rotation matrix. -pub trait RotationMatrix, AV> : Rotation { - /// The output rotation matrix type. - type Output: SquareMatrix + Rotation; - - /// Gets the rotation matrix represented by `self`. - fn to_rotation_matrix(&self) -> Self::Output; -} - -/// Composition of a rotation and an absolute value. -/// -/// The operation is accessible using the `RotationMatrix`, `Absolute`, and `RMul` traits, but -/// doing so is not easy in generic code as it can be a cause of type over-parametrization. -pub trait AbsoluteRotate { - /// This is the same as: - /// - /// ```.ignore - /// self.rotation_matrix().absolute().rmul(v) - /// ``` - fn absolute_rotate(&self, v: &V) -> V; -} - -/// Trait of object which represent a transformation, and to which new transformations can -/// be appended. -/// -/// A transformation is assumed to be an isometry without reflexion. -pub trait Transformation { - /// Gets the transformation of `self`. - fn transformation(&self) -> M; - - /// Gets the inverse transformation of `self`. - fn inverse_transformation(&self) -> M; - - /// Appends a transformation to this object. - fn append_transformation_mut(&mut self, &M); - - /// Appends the transformation `amount` to a copy of `t`. - fn append_transformation(&self, amount: &M) -> Self; - - /// Prepends a transformation to this object. - fn prepend_transformation_mut(&mut self, &M); - - /// Prepends the transformation `amount` to a copy of `t`. - fn prepend_transformation(&self, amount: &M) -> Self; - - /// Sets the transformation of `self`. - fn set_transformation(&mut self, M); -} - -/// Trait of objects able to transform other objects. -/// -/// This is typically implemented by matrices which transform vectors. -pub trait Transform { - /// Applies a transformation to `v`. - fn transform(&self, &V) -> V; - - /// Applies an inverse transformation to `v`. - fn inverse_transform(&self, &V) -> V; -} - -/// Traits of objects having a dot product. -pub trait Dot { - /// Computes the dot (inner) product of two vectors. - #[inline] - fn dot(&self, other: &Self) -> N; -} - -/// Traits of objects having an euclidian norm. -pub trait Norm: Sized { - /// The scalar type for the norm (i.e. the undelying field). - type NormType : BaseFloat; - - /// Computes the norm of `self`. - #[inline] - fn norm(&self) -> Self::NormType { - self.norm_squared().sqrt() - } - - /// Computes the squared norm of `self`. - /// - /// This is usually faster than computing the norm itself. - fn norm_squared(&self) -> Self::NormType; - - /// Gets the normalized version of a copy of `v`. - /// - /// Might return an invalid result if the vector is zero or close to zero. - fn normalize(&self) -> Self; - - /// Normalizes `self`. - /// - /// The result might be invalid if the vector is zero or close to zero. - fn normalize_mut(&mut self) -> Self::NormType; - - /// Gets the normalized version of a copy of `v` or `None` if the vector has a norm smaller - /// or equal to `min_norm`. In particular, `.try_normalize(0.0)` returns `None` if the norm is - /// exactly zero. - fn try_normalize(&self, min_norm: Self::NormType) -> Option; - - /// Normalized `v` or does nothing if the vector has a norm smaller - /// or equal to `min_norm`. - /// - /// Returns the old norm or `None` if the normalization failed. - fn try_normalize_mut(&mut self, min_norm: Self::NormType) -> Option; -} - -/** - * Trait of elements having a cross product. - */ -pub trait Cross { - /// The cross product output. - type CrossProductType; - - /// Computes the cross product between two elements (usually vectors). - fn cross(&self, other: &Self) -> Self::CrossProductType; -} - -/** - * Trait of elements having a cross product operation which can be expressed as a matrix. - */ -pub trait CrossMatrix { - /// The matrix associated to any cross product with this vector. I.e. `v.cross(anything)` = - /// `v.cross_matrix().rmul(anything)`. - fn cross_matrix(&self) -> M; -} - -/// Traits of objects which can be put in homogeneous coordinates form. -pub trait ToHomogeneous { - /// Gets the homogeneous coordinates form of this object. - fn to_homogeneous(&self) -> U; -} - -/// Traits of objects which can be build from an homogeneous coordinate form. -pub trait FromHomogeneous { - /// Builds an object from its homogeneous coordinate form. - /// - /// Note that this this is not required that `from` is the inverse of `to_homogeneous`. - /// Typically, `from` will remove some informations unrecoverable by `to_homogeneous`. - fn from(&U) -> Self; -} - -/// Trait of vectors able to sample a unit sphere. -/// -/// The number of sample must be sufficient to approximate a sphere using a support mapping -/// function. -pub trait UniformSphereSample : Sized { - /// Iterate through the samples. - fn sample(F); -} - -/// The zero element of a vector space, seen as an element of its embeding affine space. -// XXX: once associated types are suported, move this to the `AnyPoint` trait. -pub trait Origin { - /// The trivial origin. - fn origin() -> Self; - /// Returns true if this points is exactly the trivial origin. - fn is_origin(&self) -> bool; -} diff --git a/src/traits/mod.rs b/src/traits/mod.rs index ccf756bf..af1918e6 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -1,19 +1,3 @@ -//! Matrixhematical traits. +pub use self::axpy::Axpy; -pub use traits::geometry::{AbsoluteRotate, Cross, CrossMatrix, Dot, FromHomogeneous, Norm, Origin, - Rotate, Rotation, RotationMatrix, RotationWithTranslation, RotationTo, - ToHomogeneous, Transform, Transformation, Translate, Translation, - UniformSphereSample}; - -pub use traits::structure::{FloatVector, FloatPoint, Basis, Cast, Column, Dimension, Indexable, Iterable, - IterableMut, Matrix, SquareMatrix, Row, NumVector, NumPoint, PointAsVector, ColumnSlice, - RowSlice, Diagonal, DiagonalMut, Eye, Repeat, Shape, BaseFloat, BaseNum, - Bounded}; - -pub use traits::operations::{Absolute, ApproxEq, Axpy, Covariance, Determinant, Inverse, Mean, Outer, PartialOrder, Transpose, - EigenQR}; -pub use traits::operations::PartialOrdering; - -pub mod geometry; -pub mod structure; -pub mod operations; +mod axpy; diff --git a/src/traits/operations.rs b/src/traits/operations.rs deleted file mode 100644 index 8a7a7066..00000000 --- a/src/traits/operations.rs +++ /dev/null @@ -1,412 +0,0 @@ -//! Low level operations on vectors and matrices. - -use std::{mem, f32, f64}; -use num::Signed; -use std::ops::Mul; -use std::cmp::Ordering; -use traits::structure::SquareMatrix; - -/// Result of a partial ordering. -#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] -pub enum PartialOrdering { - /// Result of a strict comparison. - PartialLess, - /// Equality relationship. - PartialEqual, - /// Result of a strict comparison. - PartialGreater, - /// Result of a comparison between two objects that are not comparable. - NotComparable -} - -impl PartialOrdering { - /// Returns `true` if `self` is equal to `Equal`. - pub fn is_eq(&self) -> bool { - *self == PartialOrdering::PartialEqual - } - - /// Returns `true` if `self` is equal to `Less`. - pub fn is_lt(&self) -> bool { - *self == PartialOrdering::PartialLess - } - - /// Returns `true` if `self` is equal to `Less` or `Equal`. - pub fn is_le(&self) -> bool { - *self == PartialOrdering::PartialLess || *self == PartialOrdering::PartialEqual - } - - /// Returns `true` if `self` is equal to `Greater`. - pub fn is_gt(&self) -> bool { - *self == PartialOrdering::PartialGreater - } - - /// Returns `true` if `self` is equal to `Greater` or `Equal`. - pub fn is_ge(&self) -> bool { - *self == PartialOrdering::PartialGreater || *self == PartialOrdering::PartialEqual - } - - /// Returns `true` if `self` is equal to `NotComparable`. - pub fn is_not_comparable(&self) -> bool { - *self == PartialOrdering::NotComparable - } - - /// Creates a `PartialOrdering` from an `Ordering`. - pub fn from_ordering(ord: Ordering) -> PartialOrdering { - match ord { - Ordering::Less => PartialOrdering::PartialLess, - Ordering::Equal => PartialOrdering::PartialEqual, - Ordering::Greater => PartialOrdering::PartialGreater - } - } - - /// Converts this `PartialOrdering` to an `Ordering`. - /// - /// Returns `None` if `self` is `NotComparable`. - pub fn to_ordering(self) -> Option { - match self { - PartialOrdering::PartialLess => Some(Ordering::Less), - PartialOrdering::PartialEqual => Some(Ordering::Equal), - PartialOrdering::PartialGreater => Some(Ordering::Greater), - PartialOrdering::NotComparable => None - } - } -} - -/// Pointwise ordering operations. -pub trait PartialOrder { - /// Returns the infimum of this value and another - fn inf(&self, other: &Self) -> Self; - - /// Returns the supremum of this value and another - fn sup(&self, other: &Self) -> Self; - - /// Compare `self` and `other` using a partial ordering relation. - fn partial_cmp(&self, other: &Self) -> PartialOrdering; - - /// Returns `true` iff `self` and `other` are comparable and `self <= other`. - #[inline] - fn partial_le(&self, other: &Self) -> bool { - PartialOrder::partial_cmp(self, other).is_le() - } - - /// Returns `true` iff `self` and `other` are comparable and `self < other`. - #[inline] - fn partial_lt(&self, other: &Self) -> bool { - PartialOrder::partial_cmp(self, other).is_lt() - } - - /// Returns `true` iff `self` and `other` are comparable and `self >= other`. - #[inline] - fn partial_ge(&self, other: &Self) -> bool { - PartialOrder::partial_cmp(self, other).is_ge() - } - - /// Returns `true` iff `self` and `other` are comparable and `self > other`. - #[inline] - fn partial_gt(&self, other: &Self) -> bool { - PartialOrder::partial_cmp(self, other).is_gt() - } - - /// Return the minimum of `self` and `other` if they are comparable. - #[inline] - fn partial_min<'a>(&'a self, other: &'a Self) -> Option<&'a Self> { - match PartialOrder::partial_cmp(self, other) { - PartialOrdering::PartialLess | PartialOrdering::PartialEqual => Some(self), - PartialOrdering::PartialGreater => Some(other), - PartialOrdering::NotComparable => None - } - } - - /// Return the maximum of `self` and `other` if they are comparable. - #[inline] - fn partial_max<'a>(&'a self, other: &'a Self) -> Option<&'a Self> { - match PartialOrder::partial_cmp(self, other) { - PartialOrdering::PartialGreater | PartialOrdering::PartialEqual => Some(self), - PartialOrdering::PartialLess => Some(other), - PartialOrdering::NotComparable => None - } - } - - /// Clamp `value` between `min` and `max`. Returns `None` if `value` is not comparable to - /// `min` or `max`. - #[inline] - fn partial_clamp<'a>(&'a self, min: &'a Self, max: &'a Self) -> Option<&'a Self> { - let v_min = self.partial_cmp(min); - let v_max = self.partial_cmp(max); - - if v_min.is_not_comparable() || v_max.is_not_comparable() { - None - } - else if v_min.is_lt() { - Some(min) - } - else if v_max.is_gt() { - Some(max) - } - else { - Some(self) - } - } -} - -/// Trait for testing approximate equality -pub trait ApproxEq: Sized { - /// Default epsilon for approximation. - fn approx_epsilon() -> Eps; - - /// Tests approximate equality using a custom epsilon. - fn approx_eq_eps(&self, other: &Self, epsilon: &Eps) -> bool; - - /// Default ULPs for approximation. - fn approx_ulps() -> u32; - - /// Tests approximate equality using units in the last place (ULPs) - fn approx_eq_ulps(&self, other: &Self, ulps: u32) -> bool; - - /// Tests approximate equality. - #[inline] - fn approx_eq(&self, other: &Self) -> bool { - self.approx_eq_eps(other, &Self::approx_epsilon()) - } -} - -impl ApproxEq for f32 { - #[inline] - fn approx_epsilon() -> f32 { - 1.0e-6 - } - - #[inline] - fn approx_eq_eps(&self, other: &f32, epsilon: &f32) -> bool { - ::abs(&(*self - *other)) < *epsilon - } - - fn approx_ulps() -> u32 { - 8 - } - - fn approx_eq_ulps(&self, other: &f32, ulps: u32) -> bool { - // Handle -0 == +0 - if *self == *other { return true; } - - // Otherwise, differing signs should be not-equal, even if within ulps - if self.signum() != other.signum() { return false; } - - // IEEE754 floats are in the same order as 2s complement isizes - // so this trick (subtracting the isizes) works. - let iself: i32 = unsafe { mem::transmute(*self) }; - let iother: i32 = unsafe { mem::transmute(*other) }; - - (iself - iother).abs() < ulps as i32 - } -} - -impl ApproxEq for f64 { - #[inline] - fn approx_epsilon() -> f64 { - 1.0e-6 - } - - #[inline] - fn approx_eq_eps(&self, other: &f64, approx_epsilon: &f64) -> bool { - ::abs(&(*self - *other)) < *approx_epsilon - } - - fn approx_ulps() -> u32 { - 8 - } - - fn approx_eq_ulps(&self, other: &f64, ulps: u32) -> bool { - // Handle -0 == +0 - if *self == *other { return true; } - - // Otherwise, differing signs should be not-equal, even if within ulps - if self.signum() != other.signum() { return false; } - - let iself: i64 = unsafe { mem::transmute(*self) }; - let iother: i64 = unsafe { mem::transmute(*other) }; - - (iself - iother).abs() < ulps as i64 - } -} - -impl<'a, N, T: ApproxEq> ApproxEq for &'a T { - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - fn approx_eq_eps(&self, other: &&'a T, approx_epsilon: &N) -> bool { - ApproxEq::approx_eq_eps(*self, *other, approx_epsilon) - } - - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - fn approx_eq_ulps(&self, other: &&'a T, ulps: u32) -> bool { - ApproxEq::approx_eq_ulps(*self, *other, ulps) - } -} - -impl<'a, N, T: ApproxEq> ApproxEq for &'a mut T { - fn approx_epsilon() -> N { - >::approx_epsilon() - } - - fn approx_eq_eps(&self, other: &&'a mut T, approx_epsilon: &N) -> bool { - ApproxEq::approx_eq_eps(*self, *other, approx_epsilon) - } - - fn approx_ulps() -> u32 { - >::approx_ulps() - } - - fn approx_eq_ulps(&self, other: &&'a mut T, ulps: u32) -> bool { - ApproxEq::approx_eq_ulps(*self, *other, ulps) - } -} - -/// Trait of objects having an absolute value. -/// This is useful if the object does not have the same type as its absolute value. -pub trait Absolute { - /// Computes some absolute value of this object. - /// Typically, this will make all component of a matrix or vector positive. - fn abs(&Self) -> A; -} - -/// Trait of objects having an inverse. Typically used to implement matrix inverse. -pub trait Inverse: Sized { - /// Returns the inverse of `m`. - fn inverse(&self) -> Option; - - /// In-place version of `inverse`. - fn inverse_mut(&mut self) -> bool; -} - -/// Trait of objects having a determinant. Typically used by square matrices. -pub trait Determinant { - /// Returns the determinant of `m`. - fn determinant(&self) -> N; -} - -/// Trait of objects which can be transposed. -pub trait Transpose { - /// Computes the transpose of a matrix. - fn transpose(&self) -> Self; - - /// In-place version of `transposed`. - fn transpose_mut(&mut self); -} - -/// Traits of objects having an outer product. -pub trait Outer { - /// Result type of the outer product. - type OuterProductType; - - /// Computes the outer product: `a * b` - fn outer(&self, other: &Self) -> Self::OuterProductType; -} - -/// Trait for computing the covariance of a set of data. -pub trait Covariance { - /// Computes the covariance of the obsevations stored by `m`: - /// - /// * For matrices, observations are stored in its rows. - /// * For vectors, observations are stored in its components (thus are 1-dimensional). - fn covariance(&self) -> M; - - /// Computes the covariance of the obsevations stored by `m`: - /// - /// * For matrices, observations are stored in its rows. - /// * For vectors, observations are stored in its components (thus are 1-dimensional). - fn covariance_to(&self, out: &mut M) { - *out = self.covariance() - } -} - -/// Trait for computing the mean of a set of data. -pub trait Mean { - /// Computes the mean of the observations stored by `v`. - /// - /// * For matrices, observations are stored in its rows. - /// * For vectors, observations are stored in its components (thus are 1-dimensional). - fn mean(&self) -> N; -} - -/// Trait for computing the eigenvector and eigenvalues of a square matrix usin the QR algorithm. -pub trait EigenQR>: SquareMatrix { - /// Computes the eigenvectors and eigenvalues of this matrix. - fn eigen_qr(&self, eps: &N, niter: usize) -> (Self, V); -} - -/// Trait of objects implementing the `y = ax + y` operation. -pub trait Axpy { - /// Adds $$a * x$$ to `self`. - fn axpy(&mut self, a: &N, x: &Self); -} - -/* - * - * - * Some implementations for scalar types. - * - * - */ -// FIXME: move this to another module ? -macro_rules! impl_absolute( - ($n: ty) => { - impl Absolute<$n> for $n { - #[inline] - fn abs(n: &$n) -> $n { - n.abs() - } - } - } -); -macro_rules! impl_absolute_id( - ($n: ty) => { - impl Absolute<$n> for $n { - #[inline] - fn abs(n: &$n) -> $n { - *n - } - } - } -); - -impl_absolute!(f32); -impl_absolute!(f64); -impl_absolute!(i8); -impl_absolute!(i16); -impl_absolute!(i32); -impl_absolute!(i64); -impl_absolute!(isize); -impl_absolute_id!(u8); -impl_absolute_id!(u16); -impl_absolute_id!(u32); -impl_absolute_id!(u64); -impl_absolute_id!(usize); - -macro_rules! impl_axpy( - ($n: ty) => { - impl Axpy<$n> for $n { - #[inline] - fn axpy(&mut self, a: &$n, x: &$n) { - *self = *self + *a * *x - } - } - } -); - -impl_axpy!(f32); -impl_axpy!(f64); -impl_axpy!(i8); -impl_axpy!(i16); -impl_axpy!(i32); -impl_axpy!(i64); -impl_axpy!(isize); -impl_axpy!(u8); -impl_axpy!(u16); -impl_axpy!(u32); -impl_axpy!(u64); -impl_axpy!(usize); diff --git a/src/traits/structure.rs b/src/traits/structure.rs deleted file mode 100644 index 02e6d165..00000000 --- a/src/traits/structure.rs +++ /dev/null @@ -1,441 +0,0 @@ -//! Traits giving structural informations on linear algebra objects or the space they live in. - -use std::{f32, f64, i8, i16, i32, i64, u8, u16, u32, u64, isize, usize}; -use std::slice::{Iter, IterMut}; -use std::ops::{Add, Sub, Mul, Div, Rem, - AddAssign, SubAssign, MulAssign, DivAssign, RemAssign, - Index, IndexMut, Neg}; -use num::{Float, Zero, One}; -use traits::operations::{Axpy, Transpose, Inverse, Absolute, ApproxEq}; -use traits::geometry::{Dot, Norm, Origin}; - -/// Basic integral numeric trait. -pub trait BaseNum: Copy + Zero + One + - Add + Sub + - Mul + Div + - Rem + - AddAssign + SubAssign + - MulAssign + DivAssign + - RemAssign + - PartialEq + Absolute + Axpy { -} - -/// Basic floating-point number numeric trait. -pub trait BaseFloat: Float + Cast + BaseNum + ApproxEq + Neg { - /// Archimedes' constant. - fn pi() -> Self; - /// 2.0 * pi. - fn two_pi() -> Self; - /// pi / 2.0. - fn frac_pi_2() -> Self; - /// pi / 3.0. - fn frac_pi_3() -> Self; - /// pi / 4.0. - fn frac_pi_4() -> Self; - /// pi / 6.0. - fn frac_pi_6() -> Self; - /// pi / 8.0. - fn frac_pi_8() -> Self; - /// 1.0 / pi. - fn frac_1_pi() -> Self; - /// 2.0 / pi. - fn frac_2_pi() -> Self; - /// 2.0 / sqrt(pi). - fn frac_2_sqrt_pi() -> Self; - - /// Euler's number. - fn e() -> Self; - /// log2(e). - fn log2_e() -> Self; - /// log10(e). - fn log10_e() -> Self; - /// ln(2.0). - fn ln_2() -> Self; - /// ln(10.0). - fn ln_10() -> Self; -} - -/// Traits of objects which can be created from an object of type `T`. -pub trait Cast { - /// Converts an element of type `T` to an element of type `Self`. - fn from(t: T) -> Self; -} - -/// Trait of matrices. -/// -/// A matrix has rows and columns and are able to multiply them. -pub trait Matrix>: Sized + - Row + Column + Mul + - Index<(usize, usize), Output = N> -{ } - -impl Matrix for M - where M: Row + Column + Mul + Index<(usize, usize), Output = N>, - C: Mul, -{ } - -/// Trait implemented by square matrices. -pub trait SquareMatrix>: Matrix + - Mul + - Eye + Transpose + Diagonal + Inverse + Dimension + One { -} - -impl SquareMatrix for M - where M: Matrix + Mul + Eye + Transpose + Diagonal + Inverse + Dimension + One, - V: Mul { -} - -/// Trait for constructing the identity matrix -pub trait Eye { - /// Return the identity matrix of specified dimension - fn new_identity(dimension: usize) -> Self; -} - -/// Trait for constructiong an object repeating a value. -pub trait Repeat { - /// Returns a value with filled by `val`. - fn repeat(val: N) -> Self; -} - -/// Types that have maximum and minimum value. -pub trait Bounded { - /// The minimum value. - #[inline] - fn min_value() -> Self; - /// The maximum value. - #[inline] - fn max_value() -> Self; -} - -// FIXME: return an iterator instead -/// Traits of objects which can form a basis (typically vectors). -pub trait Basis: Sized { - /// Iterates through the canonical basis of the space in which this object lives. - fn canonical_basis bool>(F); - - /// Iterates through a basis of the subspace orthogonal to `self`. - fn orthonormal_subspace_basis bool>(&Self, F); - - /// Gets the ith element of the canonical basis. - fn canonical_basis_element(i: usize) -> Option; -} - -/// Trait to access rows of a matrix or a vector. -pub trait Row { - /// The number of column of `self`. - fn nrows(&self) -> usize; - /// Reads the `i`-th row of `self`. - fn row(&self, i: usize) -> R; - /// Writes the `i`-th row of `self`. - fn set_row(&mut self, i: usize, R); - - // FIXME: add iterators on rows: this could be a very good way to generalize _and_ optimize - // a lot of operations. -} - -/// Trait to access columns of a matrix or vector. -pub trait Column { - /// The number of column of this matrix or vector. - fn ncols(&self) -> usize; - - /// Reads the `i`-th column of `self`. - fn column(&self, i: usize) -> C; - - /// Writes the `i`-th column of `self`. - fn set_column(&mut self, i: usize, C); - - // FIXME: add iterators on columns: this could be a very good way to generalize _and_ optimize - // a lot of operations. -} - -/// Trait to access part of a column of a matrix -pub trait ColumnSlice { - /// Returns a view to a slice of a column of a matrix. - fn column_slice(&self, column_id: usize, row_start: usize, row_end: usize) -> C; -} - -/// Trait to access part of a row of a matrix -pub trait RowSlice { - /// Returns a view to a slice of a row of a matrix. - fn row_slice(&self, row_id: usize, column_start: usize, column_end: usize) -> R; -} - -/// Trait of objects having a spacial dimension known at compile time. -pub trait Dimension: Sized { - /// The dimension of the object. - fn dimension(_unused: Option) -> usize; -} - -/// Trait to get the diagonal of square matrices. -pub trait Diagonal { - /// Creates a new matrix with the given diagonal. - fn from_diagonal(diagonal: &V) -> Self; - - /// The diagonal of this matrix. - fn diagonal(&self) -> V; -} - -/// Trait to set the diagonal of square matrices. -pub trait DiagonalMut: Diagonal { - /// Sets the diagonal of this matrix. - fn set_diagonal(&mut self, diagonal: &V); -} - -/// The shape of an indexable object. -pub trait Shape: Index { - /// Returns the shape of an indexable object. - fn shape(&self) -> I; -} - -/// This is a workaround of current Rust limitations. -/// -/// It exists because the `I` trait cannot be used to express write access. -/// Thus, this is the same as the `I` trait but without the syntactic sugar and with a method -/// to write to a specific index. -pub trait Indexable: Shape + IndexMut { - /// Swaps the `i`-th element of `self` with its `j`-th element. - fn swap(&mut self, i: I, j: I); - - /// Reads the `i`-th element of `self`. - /// - /// `i` is not checked. - unsafe fn unsafe_at(&self, i: I) -> N; - /// Writes to the `i`-th element of `self`. - /// - /// `i` is not checked. - unsafe fn unsafe_set(&mut self, i: I, N); -} - -/// This is a workaround of current Rust limitations. -/// -/// Traits of objects which can be iterated through like a vector. -pub trait Iterable { - /// Gets a vector-like read-only iterator. - fn iter(&self) -> Iter; -} - -/// This is a workaround of current Rust limitations. -/// -/// Traits of mutable objects which can be iterated through like a vector. -pub trait IterableMut { - /// Gets a vector-like read-write iterator. - fn iter_mut(&mut self) -> IterMut; -} - -/* - * Vector related traits. - */ -/// Trait grouping most common operations on vectors. -pub trait NumVector: Add + Sub + - // Mul + Div + - - // Add + Sub + - Mul + Div + - - AddAssign + SubAssign + - // MulAssign + DivAssign + - - // AddAssign + SubAssign + - MulAssign + DivAssign + - - Dimension + Index + - Zero + PartialEq + Dot + Axpy { -} - -/// Trait of vector with components implementing the `BaseFloat` trait. -pub trait FloatVector: NumVector + Norm + Neg + Basis + ApproxEq { -} - -/* - * Point related traits. - */ -/// Trait that relates a point of an affine space to a vector of the associated vector space. -pub trait PointAsVector { - /// The vector type of the vector space associated to this point's affine space. - type Vector; - - /// Converts this point to its associated vector. - fn to_vector(self) -> Self::Vector; - - /// Converts a reference to this point to a reference to its associated vector. - fn as_vector(&self) -> &Self::Vector; - - // NOTE: this is used in some places to overcome some limitations untill the trait reform is - // done on rustc. - /// Sets the coordinates of this point to match those of a given vector. - fn set_coords(&mut self, coords: Self::Vector); -} - -/// Trait grouping most common operations on points. -// XXX: the vector space element `V` should be an associated type. Though this would prevent V from -// having bounds (they are not supported yet). So, for now, we will just use a type parameter. -pub trait NumPoint: - Copy + - PointAsVector + - Dimension + - Origin + - PartialEq + - Axpy + - Sub::Vector> + - - Mul + Div + - Add<::Vector, Output = Self> + - - MulAssign + DivAssign + - AddAssign<::Vector> + - - Index { // FIXME: + Sub -} - -/// Trait of points with components implementing the `BaseFloat` trait. -pub trait FloatPoint: NumPoint + Sized - where ::Vector: Norm { - /// Computes the square distance between two points. - #[inline] - fn distance_squared(&self, other: &Self) -> N { - (*self - *other).norm_squared() - } - - /// Computes the distance between two points. - #[inline] - fn distance(&self, other: &Self) -> N { - (*self - *other).norm() - } -} - -/* - * - * - * Some implementations for builtin types. - * - * - */ -// Bounded -macro_rules! impl_bounded( - ($n: ty, $min: expr, $max: expr) => { - impl Bounded for $n { - #[inline] - fn min_value() -> $n { - $min - } - - #[inline] - fn max_value() -> $n { - $max - } - } - } -); - -impl_bounded!(f32, f32::MIN, f32::MAX); -impl_bounded!(f64, f64::MIN, f64::MAX); -impl_bounded!(i8, i8::MIN, i8::MAX); -impl_bounded!(i16, i16::MIN, i16::MAX); -impl_bounded!(i32, i32::MIN, i32::MAX); -impl_bounded!(i64, i64::MIN, i64::MAX); -impl_bounded!(isize, isize::MIN, isize::MAX); -impl_bounded!(u8, u8::MIN, u8::MAX); -impl_bounded!(u16, u16::MIN, u16::MAX); -impl_bounded!(u32, u32::MIN, u32::MAX); -impl_bounded!(u64, u64::MIN, u64::MAX); -impl_bounded!(usize, usize::MIN, usize::MAX); - - -// BaseFloat -macro_rules! impl_base_float( - ($n: ident) => { - impl BaseFloat for $n { - /// Archimedes' constant. - fn pi() -> $n { - $n::consts::PI - } - - /// 2.0 * pi. - fn two_pi() -> $n { - 2.0 * $n::consts::PI - } - - /// pi / 2.0. - fn frac_pi_2() -> $n { - $n::consts::FRAC_PI_2 - } - - /// pi / 3.0. - fn frac_pi_3() -> $n { - $n::consts::FRAC_PI_3 - } - - /// pi / 4.0. - fn frac_pi_4() -> $n { - $n::consts::FRAC_PI_4 - } - - /// pi / 6.0. - fn frac_pi_6() -> $n { - $n::consts::FRAC_PI_6 - } - - /// pi / 8.0. - fn frac_pi_8() -> $n { - $n::consts::FRAC_PI_8 - } - - /// 1.0 / pi. - fn frac_1_pi() -> $n { - $n::consts::FRAC_1_PI - } - - /// 2.0 / pi. - fn frac_2_pi() -> $n { - $n::consts::FRAC_2_PI - } - - /// 2.0 / sqrt(pi). - fn frac_2_sqrt_pi() -> $n { - $n::consts::FRAC_2_SQRT_PI - } - - - /// Euler's number. - fn e() -> $n { - $n::consts::E - } - - /// log2(e). - fn log2_e() -> $n { - $n::consts::LOG2_E - } - - /// log10(e). - fn log10_e() -> $n { - $n::consts::LOG10_E - } - - /// ln(2.0). - fn ln_2() -> $n { - $n::consts::LN_2 - } - - /// ln(10.0). - fn ln_10() -> $n { - $n::consts::LN_10 - } - } - } -); - -impl BaseNum for i8 { } -impl BaseNum for i16 { } -impl BaseNum for i32 { } -impl BaseNum for i64 { } -impl BaseNum for isize { } -impl BaseNum for u8 { } -impl BaseNum for u16 { } -impl BaseNum for u32 { } -impl BaseNum for u64 { } -impl BaseNum for usize { } -impl BaseNum for f32 { } -impl BaseNum for f64 { } - -impl_base_float!(f32); -impl_base_float!(f64); diff --git a/tests/arbitrary.rs b/tests/arbitrary.rs deleted file mode 100644 index f54af223..00000000 --- a/tests/arbitrary.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![cfg(feature="arbitrary")] - -extern crate nalgebra as na; -extern crate quickcheck; -extern crate rand; - -use quickcheck::{Arbitrary, StdGen}; -use na::*; - - -macro_rules! trivial_arb_test( - ($t: ty, $name: ident) => ( - #[test] - fn $name() { - let mut g = StdGen::new(rand::thread_rng(), 100); - let _: $t = Arbitrary::arbitrary(&mut g); - } - ) -); - -trivial_arb_test!(Vector1, arb_vec1); -trivial_arb_test!(Vector2, arb_vec2); -trivial_arb_test!(Vector3, arb_vec3); -trivial_arb_test!(Vector4, arb_vec4); -trivial_arb_test!(Vector5, arb_vec5); -trivial_arb_test!(Vector6, arb_vec6); - -trivial_arb_test!(Point1, arb_point1); -trivial_arb_test!(Point2, arb_point2); -trivial_arb_test!(Point3, arb_point3); -trivial_arb_test!(Point4, arb_point4); -trivial_arb_test!(Point5, arb_point5); -trivial_arb_test!(Point6, arb_point6); - -trivial_arb_test!(Matrix1, arb_mat1); -trivial_arb_test!(Matrix2, arb_mat2); -trivial_arb_test!(Matrix3, arb_mat3); -trivial_arb_test!(Matrix4, arb_mat4); -trivial_arb_test!(Matrix5, arb_mat5); -trivial_arb_test!(Matrix6, arb_mat6); - -trivial_arb_test!(DVector1, arb_dvec1); -trivial_arb_test!(DVector2, arb_dvec2); -trivial_arb_test!(DVector3, arb_dvec3); -trivial_arb_test!(DVector4, arb_dvec4); -trivial_arb_test!(DVector5, arb_dvec5); -trivial_arb_test!(DVector6, arb_dvec6); - -trivial_arb_test!(DMatrix, arb_dmatrix); -trivial_arb_test!(DVector, arb_dvector); - -trivial_arb_test!(Quaternion, arb_quaternion); -trivial_arb_test!(UnitQuaternion, arb_unit_quaternion); - -trivial_arb_test!(Isometry2, arb_iso2); -trivial_arb_test!(Isometry3, arb_iso3); - -trivial_arb_test!(Rotation2, arb_rot2); -trivial_arb_test!(Rotation3, arb_rot3); - -trivial_arb_test!(Orthographic3, arb_ortho3); -trivial_arb_test!(OrthographicMatrix3, arb_ortho_mat3); -trivial_arb_test!(Perspective3, arb_persp3); -trivial_arb_test!(PerspectiveMatrix3, arb_perspective_mat3); diff --git a/tests/assert.rs b/tests/assert.rs deleted file mode 100644 index bd2bb33e..00000000 --- a/tests/assert.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Assertion macro tests - -#[macro_use] -extern crate nalgebra; - -use nalgebra::{ApproxEq, Vector2}; -use std::fmt::Debug; - -// Replace the assert_approx_eq! macro so that we can have type inference. -fn test_approx_eq(given: &T, expected: &T) where T: Debug + ApproxEq, N: Debug { - if !given.approx_eq(expected) { - panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)", - *given, *expected, - T::approx_epsilon() - ) - } -} - -#[test] -fn assert_approx_eq_f64() { - let a = 1.0f64; - let b = 1.0f64 + 1.0e-12f64; - test_approx_eq(&a, &b); - test_approx_eq(&(&a), &(&b)); -} - -#[test] -#[should_panic] -fn assert_approx_eq_vec2_f32_fail() { - let a = Vector2::new(1.0f32, 0.0); - let b = Vector2::new(1.1f32, 0.1); - test_approx_eq(&a, &b); -} - -#[test] -fn assert_approx_eq_eps_f32() { - assert_approx_eq_eps!(1.0f32, 1.1, 0.2); - assert_approx_eq_eps!(&mut 1.0f32, &mut 1.1, 0.2); -} - -#[test] -#[should_panic] -fn assert_approx_eq_eps_f64_fail() { - assert_approx_eq_eps!(1.0f64, 1.1, 0.05); -} - -#[test] -fn assert_approx_eq_ulps_f32() { - let x = 1000000_f32; - let y = 1000000.1_f32; - assert!(x != y); - assert_approx_eq_ulps!(x, y, 3); - assert_approx_eq_ulps!(&x, &y, 3); -} - -#[test] -#[should_panic] -fn assert_approx_eq_ulps_f32_fail() { - let x = 1000000_f32; - let y = 1000000.1_f32; - assert_approx_eq_ulps!(x, y, 2); -} - -#[test] -fn assert_approx_eq_ulps_f64() { - let x = 1000000_f64; - let y = 1000000.0000000003_f64; - assert!(x != y); - assert_approx_eq_ulps!(x, y, 4); -} - -#[test] -#[should_panic] -fn assert_approx_eq_ulps_f64_fail() { - let x = 1000000_f64; - let y = 1000000.0000000003_f64; - assert!(x != y); - assert_approx_eq_ulps!(x, y, 3); -} diff --git a/tests/conversion.rs b/tests/conversion.rs new file mode 100644 index 00000000..558b6a6f --- /dev/null +++ b/tests/conversion.rs @@ -0,0 +1,147 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use alga::linear::Transformation; +use na::{Vector3, Point3, Translation3, Isometry3, Similarity3, Affine3, Projective3, Transform3, + Rotation3, UnitQuaternion}; + + +#[cfg(feature = "arbitrary")] +quickcheck!{ + fn translation_conversion(t: Translation3, v: Vector3, p: Point3) -> bool { + let iso: Isometry3 = na::convert(t); + let sim: Similarity3 = na::convert(t); + let aff: Affine3 = na::convert(t); + let prj: Projective3 = na::convert(t); + let tr: Transform3 = na::convert(t); + + t == na::try_convert(iso).unwrap() && + t == na::try_convert(sim).unwrap() && + t == na::try_convert(aff).unwrap() && + t == na::try_convert(prj).unwrap() && + t == na::try_convert(tr).unwrap() && + + t.transform_vector(&v) == iso * v && + t.transform_vector(&v) == sim * v && + t.transform_vector(&v) == aff * v && + t.transform_vector(&v) == prj * v && + t.transform_vector(&v) == tr * v && + + t * p == iso * p && + t * p == sim * p && + t * p == aff * p && + t * p == prj * p && + t * p == tr * p + } + + fn rotation_conversion(r: Rotation3, v: Vector3, p: Point3) -> bool { + let uq: UnitQuaternion = na::convert(r); + let iso: Isometry3 = na::convert(r); + let sim: Similarity3 = na::convert(r); + let aff: Affine3 = na::convert(r); + let prj: Projective3 = na::convert(r); + let tr: Transform3 = na::convert(r); + + relative_eq!(r, na::try_convert(uq).unwrap(), epsilon = 1.0e-7) && + relative_eq!(r, na::try_convert(iso).unwrap(), epsilon = 1.0e-7) && + relative_eq!(r, na::try_convert(sim).unwrap(), epsilon = 1.0e-7) && + r == na::try_convert(aff).unwrap() && + r == na::try_convert(prj).unwrap() && + r == na::try_convert(tr).unwrap() && + + // NOTE: we need relative_eq because IsometryBase and SimilarityBase use quaternions. + relative_eq!(r * v, uq * v, epsilon = 1.0e-7) && + relative_eq!(r * v, iso * v, epsilon = 1.0e-7) && + relative_eq!(r * v, sim * v, epsilon = 1.0e-7) && + r * v == aff * v && + r * v == prj * v && + r * v == tr * v && + + relative_eq!(r * p, uq * p, epsilon = 1.0e-7) && + relative_eq!(r * p, iso * p, epsilon = 1.0e-7) && + relative_eq!(r * p, sim * p, epsilon = 1.0e-7) && + r * p == aff * p && + r * p == prj * p && + r * p == tr * p + } + + fn unit_quaternion_conversion(uq: UnitQuaternion, v: Vector3, p: Point3) -> bool { + let rot: Rotation3 = na::convert(uq); + let iso: Isometry3 = na::convert(uq); + let sim: Similarity3 = na::convert(uq); + let aff: Affine3 = na::convert(uq); + let prj: Projective3 = na::convert(uq); + let tr: Transform3 = na::convert(uq); + + uq == na::try_convert(iso).unwrap() && + uq == na::try_convert(sim).unwrap() && + relative_eq!(uq, na::try_convert(rot).unwrap(), epsilon = 1.0e-7) && + relative_eq!(uq, na::try_convert(aff).unwrap(), epsilon = 1.0e-7) && + relative_eq!(uq, na::try_convert(prj).unwrap(), epsilon = 1.0e-7) && + relative_eq!(uq, na::try_convert(tr).unwrap(), epsilon = 1.0e-7) && + + // NOTE: iso and sim use unit quaternions for the rotation so conversions to them are exact. + relative_eq!(uq * v, rot * v, epsilon = 1.0e-7) && + uq * v == iso * v && + uq * v == sim * v && + relative_eq!(uq * v, aff * v, epsilon = 1.0e-7) && + relative_eq!(uq * v, prj * v, epsilon = 1.0e-7) && + relative_eq!(uq * v, tr * v, epsilon = 1.0e-7) && + + relative_eq!(uq * p, rot * p, epsilon = 1.0e-7) && + uq * p == iso * p && + uq * p == sim * p && + relative_eq!(uq * p, aff * p, epsilon = 1.0e-7) && + relative_eq!(uq * p, prj * p, epsilon = 1.0e-7) && + relative_eq!(uq * p, tr * p, epsilon = 1.0e-7) + } + + fn isometry_conversion(iso: Isometry3, v: Vector3, p: Point3) -> bool { + let sim: Similarity3 = na::convert(iso); + let aff: Affine3 = na::convert(iso); + let prj: Projective3 = na::convert(iso); + let tr: Transform3 = na::convert(iso); + + + iso == na::try_convert(sim).unwrap() && + relative_eq!(iso, na::try_convert(aff).unwrap(), epsilon = 1.0e-7) && + relative_eq!(iso, na::try_convert(prj).unwrap(), epsilon = 1.0e-7) && + relative_eq!(iso, na::try_convert(tr).unwrap(), epsilon = 1.0e-7) && + + iso * v == sim * v && + relative_eq!(iso * v, aff * v, epsilon = 1.0e-7) && + relative_eq!(iso * v, prj * v, epsilon = 1.0e-7) && + relative_eq!(iso * v, tr * v, epsilon = 1.0e-7) && + + iso * p == sim * p && + relative_eq!(iso * p, aff * p, epsilon = 1.0e-7) && + relative_eq!(iso * p, prj * p, epsilon = 1.0e-7) && + relative_eq!(iso * p, tr * p, epsilon = 1.0e-7) + } + + fn similarity_conversion(sim: Similarity3, v: Vector3, p: Point3) -> bool { + let aff: Affine3 = na::convert(sim); + let prj: Projective3 = na::convert(sim); + let tr: Transform3 = na::convert(sim); + + relative_eq!(sim, na::try_convert(aff).unwrap(), epsilon = 1.0e-7) && + relative_eq!(sim, na::try_convert(prj).unwrap(), epsilon = 1.0e-7) && + relative_eq!(sim, na::try_convert(tr).unwrap(), epsilon = 1.0e-7) && + + relative_eq!(sim * v, aff * v, epsilon = 1.0e-7) && + relative_eq!(sim * v, prj * v, epsilon = 1.0e-7) && + relative_eq!(sim * v, tr * v, epsilon = 1.0e-7) && + + relative_eq!(sim * p, aff * p, epsilon = 1.0e-7) && + relative_eq!(sim * p, prj * p, epsilon = 1.0e-7) && + relative_eq!(sim * p, tr * p, epsilon = 1.0e-7) + } + + // XXX test TransformBase +} diff --git a/tests/isometry.rs b/tests/isometry.rs new file mode 100644 index 00000000..7c04fac8 --- /dev/null +++ b/tests/isometry.rs @@ -0,0 +1,222 @@ +#![allow(non_snake_case)] + +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use alga::linear::{Transformation, ProjectiveTransformation}; +use na::{Vector3, Point3, Rotation3, Isometry3, Translation3, UnitQuaternion}; + +quickcheck!( + fn append_rotation_wrt_point_to_id(r: UnitQuaternion, p: Point3) -> bool { + let mut iso = Isometry3::identity(); + iso.append_rotation_wrt_point_mut(&r, &p); + + iso == Isometry3::rotation_wrt_point(r, p) + } + + fn rotation_wrt_point_invariance(r: UnitQuaternion, p: Point3) -> bool { + let iso = Isometry3::rotation_wrt_point(r, p); + + relative_eq!(iso * p, p) + } + + fn look_at_rh_3(eye: Point3, target: Point3, up: Vector3) -> bool { + let viewmatrix = Isometry3::look_at_rh(&eye, &target, &up); + + let origin = Point3::origin(); + relative_eq!(viewmatrix * eye, origin, epsilon = 1.0e-7) && + relative_eq!((viewmatrix * (target - eye)).normalize(), -Vector3::z(), epsilon = 1.0e-7) + } + + fn observer_frame_3(eye: Point3, target: Point3, up: Vector3) -> bool { + let observer = Isometry3::new_observer_frame(&eye, &target, &up); + + let origin = Point3::origin(); + relative_eq!(observer * origin, eye, epsilon = 1.0e-7) && + relative_eq!(observer * Vector3::z(), (target - eye).normalize(), epsilon = 1.0e-7) + } + + fn inverse_is_identity(i: Isometry3, p: Point3, v: Vector3) -> bool { + let ii = i.inverse(); + + relative_eq!(i * ii, Isometry3::identity(), epsilon = 1.0e-7) && + relative_eq!(ii * i, Isometry3::identity(), epsilon = 1.0e-7) && + relative_eq!((i * ii) * p, p, epsilon = 1.0e-7) && + relative_eq!((ii * i) * p, p, epsilon = 1.0e-7) && + relative_eq!((i * ii) * v, v, epsilon = 1.0e-7) && + relative_eq!((ii * i) * v, v, epsilon = 1.0e-7) + } + + fn inverse_is_parts_inversion(t: Translation3, r: UnitQuaternion) -> bool { + let i = t * r; + i.inverse() == r.inverse() * t.inverse() + } + + fn multiply_equals_alga_transform(i: Isometry3, v: Vector3, p: Point3) -> bool { + i * v == i.transform_vector(&v) && + i * p == i.transform_point(&p) && + relative_eq!(i.inverse() * v, i.inverse_transform_vector(&v), epsilon = 1.0e-7) && + relative_eq!(i.inverse() * p, i.inverse_transform_point(&p), epsilon = 1.0e-7) + } + + fn composition(i: Isometry3, uq: UnitQuaternion, r: Rotation3, + t: Translation3, v: Vector3, p: Point3) -> bool { + // (rotation × translation) * point = rotation × (translation * point) + relative_eq!((uq * t) * v, uq * v, epsilon = 1.0e-7) && + relative_eq!((r * t) * v, r * v, epsilon = 1.0e-7) && + relative_eq!((uq * t) * p, uq * (t * p), epsilon = 1.0e-7) && + relative_eq!((r * t) * p, r * (t * p), epsilon = 1.0e-7) && + + // (translation × rotation) * point = translation × (rotation * point) + (t * uq) * v == uq * v && + (t * r) * v == r * v && + (t * uq) * p == t * (uq * p) && + (t * r) * p == t * (r * p) && + + // (rotation × isometry) * point = rotation × (isometry * point) + relative_eq!((uq * i) * v, uq * (i * v), epsilon = 1.0e-7) && + relative_eq!((uq * i) * p, uq * (i * p), epsilon = 1.0e-7) && + + // (isometry × rotation) * point = isometry × (rotation * point) + relative_eq!((i * uq) * v, i * (uq * v), epsilon = 1.0e-7) && + relative_eq!((i * uq) * p, i * (uq * p), epsilon = 1.0e-7) && + + // (translation × isometry) * point = translation × (isometry * point) + relative_eq!((t * i) * v, (i * v), epsilon = 1.0e-7) && + relative_eq!((t * i) * p, t * (i * p), epsilon = 1.0e-7) && + + // (isometry × translation) * point = isometry × (translation * point) + relative_eq!((i * t) * v, i * v, epsilon = 1.0e-7) && + relative_eq!((i * t) * p, i * (t * p), epsilon = 1.0e-7) + } + + fn all_op_exist(i: Isometry3, uq: UnitQuaternion, t: Translation3, + v: Vector3, p: Point3, r: Rotation3) -> bool { + let iMi = i * i; + let iMuq = i * uq; + let iDi = i / i; + let iDuq = i / uq; + + let iMp = i * p; + let iMv = i * v; + + let iMt = i * t; + let tMi = t * i; + + let tMr = t * r; + let tMuq = t * uq; + + let uqMi = uq * i; + let uqDi = uq / i; + + let rMt = r * t; + let uqMt = uq * t; + + let mut iMt1 = i; + let mut iMt2 = i; + + let mut iMi1 = i; + let mut iMi2 = i; + + let mut iMuq1 = i; + let mut iMuq2 = i; + + let mut iDi1 = i; + let mut iDi2 = i; + + let mut iDuq1 = i; + let mut iDuq2 = i; + + iMt1 *= t; + iMt2 *= &t; + + iMi1 *= i; + iMi2 *= &i; + + iMuq1 *= uq; + iMuq2 *= &uq; + + iDi1 /= i; + iDi2 /= &i; + + iDuq1 /= uq; + iDuq2 /= &uq; + + iMt == iMt1 && + iMt == iMt2 && + + iMi == iMi1 && + iMi == iMi2 && + + iMuq == iMuq1 && + iMuq == iMuq2 && + + iDi == iDi1 && + iDi == iDi2 && + + iDuq == iDuq1 && + iDuq == iDuq2 && + + iMi == &i * &i && + iMi == i * &i && + iMi == &i * i && + + iMuq == &i * &uq && + iMuq == i * &uq && + iMuq == &i * uq && + + iDi == &i / &i && + iDi == i / &i && + iDi == &i / i && + + iDuq == &i / &uq && + iDuq == i / &uq && + iDuq == &i / uq && + + iMp == &i * &p && + iMp == i * &p && + iMp == &i * p && + + iMv == &i * &v && + iMv == i * &v && + iMv == &i * v && + + iMt == &i * &t && + iMt == i * &t && + iMt == &i * t && + + tMi == &t * &i && + tMi == t * &i && + tMi == &t * i && + + tMr == &t * &r && + tMr == t * &r && + tMr == &t * r && + + tMuq == &t * &uq && + tMuq == t * &uq && + tMuq == &t * uq && + + uqMi == &uq * &i && + uqMi == uq * &i && + uqMi == &uq * i && + + uqDi == &uq / &i && + uqDi == uq / &i && + uqDi == &uq / i && + + rMt == &r * &t && + rMt == r * &t && + rMt == &r * t && + + uqMt == &uq * &t && + uqMt == uq * &t && + uqMt == &uq * t + } +); diff --git a/tests/mat.rs b/tests/mat.rs deleted file mode 100644 index 8d3469a3..00000000 --- a/tests/mat.rs +++ /dev/null @@ -1,852 +0,0 @@ -extern crate nalgebra as na; -extern crate rand; - -use rand::random; -use na::{Rotation2, Rotation3, Isometry2, Isometry3, Similarity2, Similarity3, Vector3, Matrix1, Matrix2, Matrix3, Matrix4, Matrix5, Matrix6, DMatrix, DVector, - Row, Column, Diagonal, Transpose, RowSlice, ColumnSlice, Shape}; - -macro_rules! test_inverse_mat_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - let randmatrix : $t = random(); - - match na::inverse(&randmatrix) { - None => { }, - Some(i) => { - assert!(na::approx_eq(&(i * randmatrix), &na::one())) - } - } - } - ); -); - -macro_rules! test_transpose_mat_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - let randmatrix : $t = random(); - - assert!(na::transpose(&na::transpose(&randmatrix)) == randmatrix); - } - ); -); - -macro_rules! test_qr_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - let randmatrix : $t = random(); - - let (q, r) = na::qr(&randmatrix); - let recomp = q * r; - - assert!(na::approx_eq(&randmatrix, &recomp)); - } - ); -); - -macro_rules! test_cholesky_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - - // construct symmetric positive definite matrix - let mut randmatrix : $t = random(); - let mut diagmatrix : $t = Diagonal::from_diagonal(&na::diagonal(&randmatrix)); - - diagmatrix = na::abs(&diagmatrix) + 1.0; - randmatrix = randmatrix * diagmatrix * na::transpose(&randmatrix); - - let result = na::cholesky(&randmatrix); - - assert!(result.is_ok()); - - let v = result.unwrap(); - let recomp = v * na::transpose(&v); - assert!(na::approx_eq(&randmatrix, &recomp)); - } - ); -); - -macro_rules! test_hessenberg_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - - let randmatrix : $t = random(); - - let (q, h) = na::hessenberg(&randmatrix); - let recomp = q * h * na::transpose(&q); - - let (rows, cols) = h.shape(); - - // Check if `h` has zero entries below the first subdiagonal - if cols > 2 { - for j in 0..(cols-2) { - for i in (j+2)..rows { - assert!(na::approx_eq(&h[(i,j)], &0.0f64)); - } - } - } - - assert!(na::approx_eq(&randmatrix, &recomp)); - } - ); -); - -macro_rules! test_eigen_qr_impl( - ($t: ty) => { - for _ in 0usize .. 10000 { - let randmatrix : $t = random(); - // Make it symetric so that we can recompose the matrix to test at the end. - let randmatrix = na::transpose(&randmatrix) * randmatrix; - let (eigenvectors, eigenvalues) = na::eigen_qr(&randmatrix, &1e-13, 100); - - let diagonal: $t = Diagonal::from_diagonal(&eigenvalues); - let recomp = eigenvectors * diagonal * na::transpose(&eigenvectors); - println!("eigenvalues: {:?}", eigenvalues); - println!(" matrix: {:?}", randmatrix); - println!("recomp: {:?}", recomp); - - assert!(na::approx_eq_eps(&randmatrix, &recomp, &1.0e-2)); - } - - for _ in 0usize .. 10000 { - let randmatrix : $t = random(); - // Take only diagonal part - let randmatrix: $t = Diagonal::from_diagonal(&randmatrix.diagonal()); - let (eigenvectors, eigenvalues) = na::eigen_qr(&randmatrix, &1e-13, 100); - - let diagonal: $t = Diagonal::from_diagonal(&eigenvalues); - let recomp = eigenvectors * diagonal * na::transpose(&eigenvectors); - println!("eigenvalues: {:?}", eigenvalues); - println!(" matrix: {:?}", randmatrix); - println!("recomp: {:?}", recomp); - - assert!(na::approx_eq_eps(&randmatrix, &recomp, &1.0e-2)); - } - } -); - -#[test] -fn test_transpose_mat1() { - test_transpose_mat_impl!(Matrix1); -} - -#[test] -fn test_transpose_mat2() { - test_transpose_mat_impl!(Matrix2); -} - -#[test] -fn test_transpose_mat3() { - test_transpose_mat_impl!(Matrix3); -} - -#[test] -fn test_transpose_mat4() { - test_transpose_mat_impl!(Matrix4); -} - -#[test] -fn test_transpose_mat5() { - test_transpose_mat_impl!(Matrix5); -} - -#[test] -fn test_transpose_mat6() { - test_transpose_mat_impl!(Matrix6); -} - -#[test] -fn test_inverse_mat1() { - test_inverse_mat_impl!(Matrix1); -} - -#[test] -fn test_inverse_mat2() { - test_inverse_mat_impl!(Matrix2); -} - -#[test] -fn test_inverse_mat3() { - test_inverse_mat_impl!(Matrix3); -} - -#[test] -fn test_inverse_mat4() { - test_inverse_mat_impl!(Matrix4); -} - -#[test] -fn test_inverse_mat5() { - test_inverse_mat_impl!(Matrix5); -} - -#[test] -fn test_inverse_mat6() { - test_inverse_mat_impl!(Matrix6); -} - -#[test] -fn test_inverse_rot2() { - test_inverse_mat_impl!(Rotation2); -} - -#[test] -fn test_inverse_rot3() { - test_inverse_mat_impl!(Rotation3); -} - -#[test] -fn test_inverse_iso2() { - test_inverse_mat_impl!(Isometry2); -} - -#[test] -fn test_inverse_iso3() { - test_inverse_mat_impl!(Isometry3); -} - -#[test] -fn test_inverse_sim2() { - test_inverse_mat_impl!(Similarity2); -} - -#[test] -fn test_inverse_sim3() { - test_inverse_mat_impl!(Similarity3); -} - -#[test] -fn test_index_mat2() { - let matrix: Matrix2 = random(); - - assert!(matrix[(0, 1)] == na::transpose(&matrix)[(1, 0)]); -} - - -#[test] -fn test_mean_dmatrix() { - let matrix = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0f64, 2.0, 3.0, - 4.0f64, 5.0, 6.0, - 7.0f64, 8.0, 9.0, - ] - ); - - assert!(na::approx_eq(&na::mean(&matrix), &DVector::from_slice(3, &[4.0f64, 5.0, 6.0]))); -} - -#[test] -fn test_covariance_dmatrix() { - let matrix = DMatrix::from_row_vector( - 5, - 3, - &[ - 4.0f64, 2.0, 0.60, - 4.2f64, 2.1, 0.59, - 3.9f64, 2.0, 0.58, - 4.3f64, 2.1, 0.62, - 4.1f64, 2.2, 0.63 - ] - ); - - let expected = DMatrix::from_row_vector( - 3, - 3, - &[ - 0.025f64, 0.0075, 0.00175, - 0.0075f64, 0.007, 0.00135, - 0.00175f64, 0.00135, 0.00043 - ] - ); - - assert!(na::approx_eq(&na::covariance(&matrix), &expected)); -} - -#[test] -fn test_transpose_dmatrix() { - let matrix = DMatrix::from_row_vector( - 8, - 4, - &[ - 1u32,2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - 21, 22, 23, 24, - 25, 26, 27, 28, - 29, 30, 31, 32 - ] - ); - - assert!(na::transpose(&na::transpose(&matrix)) == matrix); -} - -#[test] -fn test_row_dmatrix() { - let matrix = DMatrix::from_row_vector( - 8, - 4, - &[ - 1u32,2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - 21, 22, 23, 24, - 25, 26, 27, 28, - 29, 30, 31, 32 - ] - ); - - assert_eq!(&DVector::from_slice(4, &[1u32, 2, 3, 4]), &matrix.row(0)); - assert_eq!(&DVector::from_slice(4, &[5u32, 6, 7, 8]), &matrix.row(1)); - assert_eq!(&DVector::from_slice(4, &[9u32, 10, 11, 12]), &matrix.row(2)); - assert_eq!(&DVector::from_slice(4, &[13u32, 14, 15, 16]), &matrix.row(3)); - assert_eq!(&DVector::from_slice(4, &[17u32, 18, 19, 20]), &matrix.row(4)); - assert_eq!(&DVector::from_slice(4, &[21u32, 22, 23, 24]), &matrix.row(5)); - assert_eq!(&DVector::from_slice(4, &[25u32, 26, 27, 28]), &matrix.row(6)); - assert_eq!(&DVector::from_slice(4, &[29u32, 30, 31, 32]), &matrix.row(7)); -} - -#[test] -fn test_row_slice_dmatrix() { - let matrix = DMatrix::from_row_vector( - 5, - 4, - &[ - 1u32,2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - ] - ); - - assert_eq!(&DVector::from_slice(4, &[1u32, 2, 3, 4]), &matrix.row_slice(0, 0, 4)); - assert_eq!(&DVector::from_slice(2, &[1u32, 2]), &matrix.row_slice(0, 0, 2)); - assert_eq!(&DVector::from_slice(2, &[10u32, 11]), &matrix.row_slice(2, 1, 3)); - assert_eq!(&DVector::from_slice(2, &[19u32, 20]), &matrix.row_slice(4, 2, 4)); -} - -#[test] -fn test_column_dmatrix() { - let matrix = DMatrix::from_row_vector( - 8, - 4, - &[ - 1u32,2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - 21, 22, 23, 24, - 25, 26, 27, 28, - 29, 30, 31, 32 - ] - ); - - assert_eq!(&DVector::from_slice(8, &[1u32, 5, 9, 13, 17, 21, 25, 29]), &matrix.column(0)); - assert_eq!(&DVector::from_slice(8, &[2u32, 6, 10, 14, 18, 22, 26, 30]), &matrix.column(1)); - assert_eq!(&DVector::from_slice(8, &[3u32, 7, 11, 15, 19, 23, 27, 31]), &matrix.column(2)); - assert_eq!(&DVector::from_slice(8, &[4u32, 8, 12, 16, 20, 24, 28, 32]), &matrix.column(3)); -} - -#[test] -fn test_column_slice_dmatrix() { - let matrix = DMatrix::from_row_vector( - 8, - 4, - &[ - 1u32,2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - 21, 22, 23, 24, - 25, 26, 27, 28, - 29, 30, 31, 32 - ] - ); - - assert_eq!(&DVector::from_slice(8, &[1u32, 5, 9, 13, 17, 21, 25, 29]), &matrix.column_slice(0, 0, 8)); - assert_eq!(&DVector::from_slice(3, &[1u32, 5, 9]), &matrix.column_slice(0, 0, 3)); - assert_eq!(&DVector::from_slice(5, &[11u32, 15, 19, 23, 27]), &matrix.column_slice(2, 2, 7)); - assert_eq!(&DVector::from_slice(2, &[28u32, 32]), &matrix.column_slice(3, 6, 8)); -} - -#[test] -fn test_dmat_from_vector() { - let mat1 = DMatrix::from_row_vector( - 8, - 4, - &[ - 1i32, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - 21, 22, 23, 24, - 25, 26, 27, 28, - 29, 30, 31, 32 - ] - ); - - let mat2 = DMatrix::from_column_vector( - 8, - 4, - &[ - 1i32, 5, 9, 13, 17, 21, 25, 29, - 2i32, 6, 10, 14, 18, 22, 26, 30, - 3i32, 7, 11, 15, 19, 23, 27, 31, - 4i32, 8, 12, 16, 20, 24, 28, 32 - ] - ); - - println!("mat1: {:?}, mat2: {:?}", mat1, mat2); - - assert!(mat1 == mat2); -} - -#[test] -fn test_dmat_addition() { - let mat1 = DMatrix::from_row_vector( - 2, - 2, - &[ - 1.0, 2.0, - 3.0, 4.0 - ] - ); - - let mat2 = DMatrix::from_row_vector( - 2, - 2, - &[ - 10.0, 20.0, - 30.0, 40.0 - ] - ); - - let res = DMatrix::from_row_vector( - 2, - 2, - &[ - 11.0, 22.0, - 33.0, 44.0 - ] - ); - - assert!((mat1.clone() + mat2.clone()) == res); - assert!((mat1.clone() + &mat2) == res); - assert!((&mat1 + mat2) == res); -} - -#[test] -fn test_dmat_multiplication() { - let mat1 = DMatrix::from_row_vector( - 2, - 2, - &[ - 1.0, 2.0, - 3.0, 4.0 - ] - ); - - let mat2 = DMatrix::from_row_vector( - 2, - 2, - &[ - 10.0, 20.0, - 30.0, 40.0 - ] - ); - - let res = DMatrix::from_row_vector( - 2, - 2, - &[ - 70.0, 100.0, - 150.0, 220.0 - ] - ); - - assert!((mat1.clone() * mat2.clone()) == res); - assert!((&mat1 * mat2.clone()) == res); - assert!((&mat1 * &mat2) == res); - assert!((mat1 * &mat2) == res); -} - -// Tests multiplication of rectangular (non-square) matrices. -#[test] -fn test_dmat_multiplication_rect() { - let mat1 = DMatrix::from_row_vector( - 1, - 2, - &[ - 1.0, 2.0, - ] - ); - - let mat2 = DMatrix::from_row_vector( - 2, - 3, - &[ - 3.0, 4.0, 5.0, - 6.0, 7.0, 8.0, - ] - ); - - let res = DMatrix::from_row_vector( - 1, - 3, - &[ - 15.0, 18.0, 21.0, - ] - ); - - assert!((mat1.clone() * mat2.clone()) == res); - assert!((&mat1 * mat2.clone()) == res); - assert!((mat1.clone() * &mat2) == res); - assert!((&mat1 * &mat2) == res); -} - -#[test] -fn test_dmat_subtraction() { - let mat1 = DMatrix::from_row_vector( - 2, - 2, - &[ - 1.0, 2.0, - 3.0, 4.0 - ] - ); - - let mat2 = DMatrix::from_row_vector( - 2, - 2, - &[ - 10.0, 20.0, - 30.0, 40.0 - ] - ); - - let res = DMatrix::from_row_vector( - 2, - 2, - &[ - -09.0, -18.0, - -27.0, -36.0 - ] - ); - - assert!((mat1.clone() - mat2.clone()) == res); - assert!((&mat1 - mat2.clone()) == res); - assert!((mat1 - &mat2) == res); -} - -#[test] -fn test_dmat_column() { - let matrix = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - 7.0, 8.0, 9.0, - ] - ); - - assert!(matrix.column(1) == DVector::from_slice(3, &[2.0, 5.0, 8.0])); -} - -#[test] -fn test_dmat_set_column() { - let mut matrix = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - 7.0, 8.0, 9.0, - ] - ); - - matrix.set_column(1, DVector::from_slice(3, &[12.0, 15.0, 18.0])); - - let expected = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0, 12.0, 3.0, - 4.0, 15.0, 6.0, - 7.0, 18.0, 9.0, - ] - ); - - assert!(matrix == expected); -} - -#[test] -fn test_dmat_row() { - let matrix = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - 7.0, 8.0, 9.0, - ] - ); - - assert!(matrix.row(1) == DVector::from_slice(3, &[4.0, 5.0, 6.0])); -} - -#[test] -fn test_dmat_set_row() { - let mut matrix = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - 7.0, 8.0, 9.0, - ] - ); - - matrix.set_row(1, DVector::from_slice(3, &[14.0, 15.0, 16.0])); - - let expected = DMatrix::from_row_vector( - 3, - 3, - &[ - 1.0, 2.0, 3.0, - 14.0, 15.0, 16.0, - 7.0, 8.0, 9.0, - ] - ); - - assert!(matrix == expected); -} - -/* FIXME: review qr decomposition to make it work with DMatrix. -#[test] -fn test_qr() { - for _ in 0usize .. 10 { - let dim1: usize = random(); - let dim2: usize = random(); - let rows = min(40, max(dim1, dim2)); - let cols = min(40, min(dim1, dim2)); - let randmatrix: DMatrix = DMatrix::new_random(rows, cols); - let (q, r) = na::qr(&randmatrix); - let recomp = q * r; - - assert!(na::approx_eq(&randmatrix, &recomp)); - } -} -*/ - -#[test] -fn test_qr_mat1() { - test_qr_impl!(Matrix1); -} - -#[test] -fn test_qr_mat2() { - test_qr_impl!(Matrix2); -} - -#[test] -fn test_qr_mat3() { - test_qr_impl!(Matrix3); -} - -#[test] -fn test_qr_mat4() { - test_qr_impl!(Matrix4); -} - -#[test] -fn test_qr_mat5() { - test_qr_impl!(Matrix5); -} - -#[test] -fn test_qr_mat6() { - test_qr_impl!(Matrix6); -} - -#[test] -fn test_eigen_qr_mat1() { - test_eigen_qr_impl!(Matrix1); -} - -#[test] -fn test_eigen_qr_mat2() { - test_eigen_qr_impl!(Matrix2); -} - -#[test] -fn test_eigen_qr_mat3() { - test_eigen_qr_impl!(Matrix3); -} - -#[test] -fn test_eigen_qr_mat4() { - test_eigen_qr_impl!(Matrix4); -} - -#[test] -fn test_eigen_qr_mat5() { - test_eigen_qr_impl!(Matrix5); -} - -#[test] -fn test_eigen_qr_mat6() { - test_eigen_qr_impl!(Matrix6); -} - -#[test] -fn test_from_fn() { - let actual: DMatrix = DMatrix::from_fn(3, 4, |i, j| 10 * i + j); - let expected: DMatrix = DMatrix::from_row_vector(3, 4, - &[ 0_0, 0_1, 0_2, 0_3, - 1_0, 1_1, 1_2, 1_3, - 2_0, 2_1, 2_2, 2_3 ]); - - assert_eq!(actual, expected); -} - -#[test] -fn test_row_3() { - let matrix = Matrix3::new(0.0f32, 1.0, 2.0, - 3.0, 4.0, 5.0, - 6.0, 7.0, 8.0); - let second_row = matrix.row(1); - let second_col = matrix.column(1); - - assert!(second_row == Vector3::new(3.0, 4.0, 5.0)); - assert!(second_col == Vector3::new(1.0, 4.0, 7.0)); -} - -#[test] -fn test_cholesky_const() { - - let a : Matrix3 = Matrix3::::new(1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 2.0, 3.0); - let g : Matrix3 = Matrix3::::new(1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0); - - let result = na::cholesky(&a); - - assert!(result.is_ok()); - - let v = result.unwrap(); - assert!(na::approx_eq(&v, &g)); - - let recomp = v * na::transpose(&v); - assert!(na::approx_eq(&recomp, &a)); -} - -#[test] -fn test_cholesky_not_spd() { - - let a : Matrix3 = Matrix3::::new(1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0, 1.0, 1.0); - - let result = na::cholesky(&a); - - assert!(result.is_err()); -} - -#[test] -fn test_cholesky_not_symmetric() { - - let a : Matrix2 = Matrix2::::new(1.0, 1.0, -1.0, 1.0); - - let result = na::cholesky(&a); - - assert!(result.is_err()); -} - -#[test] -fn test_cholesky_mat1() { - test_cholesky_impl!(Matrix1); -} - -#[test] -fn test_cholesky_mat2() { - test_cholesky_impl!(Matrix2); -} - -#[test] -fn test_cholesky_mat3() { - test_cholesky_impl!(Matrix3); -} - -#[test] -fn test_cholesky_mat4() { - test_cholesky_impl!(Matrix4); -} - -#[test] -fn test_cholesky_mat5() { - test_cholesky_impl!(Matrix5); -} - -#[test] -fn test_cholesky_mat6() { - test_cholesky_impl!(Matrix6); -} - -#[test] -fn test_hessenberg_mat1() { - test_hessenberg_impl!(Matrix1); -} - -#[test] -fn test_hessenberg_mat2() { - test_hessenberg_impl!(Matrix2); -} - -#[test] -fn test_hessenberg_mat3() { - test_hessenberg_impl!(Matrix3); -} - -#[test] -fn test_hessenberg_mat4() { - test_hessenberg_impl!(Matrix4); -} - -#[test] -fn test_hessenberg_mat5() { - test_hessenberg_impl!(Matrix5); -} - -#[test] -fn test_hessenberg_mat6() { - test_hessenberg_impl!(Matrix6); -} - -#[test] -fn test_transpose_square_matrix() { - let column_major_matrix = &[0, 1, 2, 3, - 0, 1, 2, 3, - 0, 1, 2, 3, - 0, 1, 2, 3]; - let num_rows = 4; - let num_cols = 4; - let mut matrix = DMatrix::from_column_vector(num_rows, num_cols, column_major_matrix); - matrix.transpose_mut(); - for i in 0..num_rows { - assert_eq!(&[0, 1, 2, 3], &matrix.row_slice(i, 0, num_cols)[..]); - } -} - -#[test] -fn test_outer_dvector() { - let vector = DVector::from_slice(5, &[ 1.0, 2.0, 3.0, 4.0, 5.0 ]); - let row = DMatrix::from_row_vector(1, 5, &vector[..]); - - assert_eq!(row.transpose() * row, na::outer(&vector, &vector)) -} diff --git a/tests/matrix.rs b/tests/matrix.rs new file mode 100644 index 00000000..21e8c262 --- /dev/null +++ b/tests/matrix.rs @@ -0,0 +1,631 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use num::{Zero, One}; +use std::fmt::Display; + +use alga::linear::FiniteDimInnerSpace; + +use na::{DVector, DMatrix, + Vector1, Vector2, Vector3, Vector4, Vector5, Vector6, + RowVector4, + Matrix1, Matrix2, Matrix3, Matrix4, Matrix5, Matrix6, + Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x3, Matrix2x4, Matrix4x6}; + + +#[test] +fn is_column_major() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + + let expected = &[ 1.0, 4.0, 2.0, 5.0, 3.0, 6.0 ]; + + assert_eq!(a.as_slice(), expected); + + let a = Matrix2x3::from_row_slice(&[1.0, 2.0, 3.0, + 4.0, 5.0, 6.0]); + + assert_eq!(a.as_slice(), expected); + + let a = Matrix2x3::from_column_slice(&[1.0, 4.0, + 2.0, 5.0, + 3.0, 6.0]); + + assert_eq!(a.as_slice(), expected); +} + +#[test] +fn linear_index() { + let a = Matrix2x3::new(1, 2, 3, + 4, 5, 6); + + assert_eq!(a[0], 1); + assert_eq!(a[1], 4); + assert_eq!(a[2], 2); + assert_eq!(a[3], 5); + assert_eq!(a[4], 3); + assert_eq!(a[5], 6); + + let b = Vector4::new(1, 2, 3, 4); + + assert_eq!(b[0], 1); + assert_eq!(b[1], 2); + assert_eq!(b[2], 3); + assert_eq!(b[3], 4); + + let c = RowVector4::new(1, 2, 3, 4); + + assert_eq!(c[0], 1); + assert_eq!(c[1], 2); + assert_eq!(c[2], 3); + assert_eq!(c[3], 4); +} + +#[test] +fn identity() { + let id1 = Matrix3::::identity(); + let id2 = Matrix3x4::new(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 id2bis = Matrix3x4::identity(); + let id3 = Matrix4x3::new(1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0); + let id3bis = Matrix4x3::identity(); + + + let not_id1 = Matrix3::identity() * 2.0; + let not_id2 = Matrix3x4::new(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 1.0); + let not_id3 = Matrix4x3::new(1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + 0.0, 1.0, 0.0); + + assert_eq!(id2, id2bis); + assert_eq!(id3, id3bis); + assert!(id1.is_identity(0.0)); + assert!(id2.is_identity(0.0)); + assert!(id3.is_identity(0.0)); + assert!(!not_id1.is_identity(0.0)); + assert!(!not_id2.is_identity(0.0)); + assert!(!not_id3.is_identity(0.0)); +} + +#[test] +fn coordinates() { + let a = Matrix3x4::new(11, 12, 13, 14, + 21, 22, 23, 24, + 31, 32, 33, 34); + + assert_eq!(a.m11, 11); + assert_eq!(a.m12, 12); + assert_eq!(a.m13, 13); + assert_eq!(a.m14, 14); + + assert_eq!(a.m21, 21); + assert_eq!(a.m22, 22); + assert_eq!(a.m23, 23); + assert_eq!(a.m24, 24); + + assert_eq!(a.m31, 31); + assert_eq!(a.m32, 32); + assert_eq!(a.m33, 33); + assert_eq!(a.m34, 34); +} + +#[test] +fn from_diagonal() { + let diag = Vector3::new(1, 2, 3); + let expected = Matrix3::new( + 1, 0, 0, + 0, 2, 0, + 0, 0, 3); + let a = Matrix3::from_diagonal(&diag); + + assert_eq!(a, expected); +} + +#[test] +fn from_rows() { + let rows = &[ + RowVector4::new(11, 12, 13, 14), + RowVector4::new(21, 22, 23, 24), + RowVector4::new(31, 32, 33, 34) + ]; + + let expected = Matrix3x4::new( + 11, 12, 13, 14, + 21, 22, 23, 24, + 31, 32, 33, 34); + + let a = Matrix3x4::from_rows(rows); + + assert_eq!(a, expected); +} + +#[test] +fn from_columns() { + let columns = &[ + Vector3::new(11, 21, 31), + Vector3::new(12, 22, 32), + Vector3::new(13, 23, 33), + Vector3::new(14, 24, 34) + ]; + + let expected = Matrix3x4::new( + 11, 12, 13, 14, + 21, 22, 23, 24, + 31, 32, 33, 34); + + let a = Matrix3x4::from_columns(columns); + + assert_eq!(a, expected); +} + +#[test] +fn from_columns_dynamic() { + let columns = &[ + DVector::from_row_slice(3, &[11, 21, 31]), + DVector::from_row_slice(3, &[12, 22, 32]), + DVector::from_row_slice(3, &[13, 23, 33]), + DVector::from_row_slice(3, &[14, 24, 34]) + ]; + + let expected = DMatrix::from_row_slice(3, 4, + &[ 11, 12, 13, 14, + 21, 22, 23, 24, + 31, 32, 33, 34 ]); + + let a = DMatrix::from_columns(columns); + + assert_eq!(a, expected); +} + +#[test] +#[should_panic] +fn from_too_many_rows() { + let rows = &[ + RowVector4::new(11, 12, 13, 14), + RowVector4::new(21, 22, 23, 24), + RowVector4::new(31, 32, 33, 34), + RowVector4::new(31, 32, 33, 34) + ]; + + let _ = Matrix3x4::from_rows(rows); +} + +#[test] +#[should_panic] +fn from_not_enough_columns() { + let columns = &[ + Vector3::new(11, 21, 31), + Vector3::new(14, 24, 34) + ]; + + let _ = Matrix3x4::from_columns(columns); +} + +#[test] +#[should_panic] +fn from_rows_with_different_dimensions() { + let columns = &[ + DVector::from_row_slice(3, &[11, 21, 31]), + DVector::from_row_slice(3, &[12, 22, 32, 33]) + ]; + + let _ = DMatrix::from_columns(columns); +} + +#[test] +fn to_homogeneous() { + let a = Vector3::new(1.0, 2.0, 3.0); + let expected_a = Vector4::new(1.0, 2.0, 3.0, 0.0); + + let b = DVector::from_row_slice(3, &[1.0, 2.0, 3.0]); + let expected_b = DVector::from_row_slice(4, &[1.0, 2.0, 3.0, 0.0]); + + assert_eq!(a.to_homogeneous(), expected_a); + assert_eq!(b.to_homogeneous(), expected_b); +} + +#[test] +fn simple_add() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + + let b = Matrix2x3::new(10.0, 20.0, 30.0, + 40.0, 50.0, 60.0); + let c = DMatrix::from_row_slice(2, 3, &[ 10.0, 20.0, 30.0, + 40.0, 50.0, 60.0 ]); + + let expected = Matrix2x3::new(11.0, 22.0, 33.0, + 44.0, 55.0, 66.0); + + assert_eq!(expected, &a + &b); + assert_eq!(expected, &a + b); + assert_eq!(expected, a + &b); + assert_eq!(expected, a + b); + + // Sum of a static matrix with a dynamic one. + assert_eq!(expected, &a + &c); + assert_eq!(expected, a + &c); + assert_eq!(expected, &c + &a); + assert_eq!(expected, &c + a); +} + +#[test] +fn simple_scalar_mul() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + + let expected = Matrix2x3::new(10.0, 20.0, 30.0, + 40.0, 50.0, 60.0); + + assert_eq!(expected, a * 10.0); + assert_eq!(expected, &a * 10.0); + assert_eq!(expected, 10.0 * a); + assert_eq!(expected, 10.0 * &a); +} + +#[test] +fn simple_mul() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + + let b = Matrix3x4::new(10.0, 20.0, 30.0, 40.0, + 50.0, 60.0, 70.0, 80.0, + 90.0, 100.0, 110.0, 120.0); + + let expected = Matrix2x4::new(380.0, 440.0, 500.0, 560.0, + 830.0, 980.0, 1130.0, 1280.0); + + assert_eq!(expected, &a * &b); + assert_eq!(expected, a * &b); + assert_eq!(expected, &a * b); + assert_eq!(expected, a * b); +} + +#[test] +fn simple_scalar_conversion() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + let expected = Matrix2x3::new(1, 2, 3, + 4, 5, 6); + + let a_u32: Matrix2x3 = na::try_convert(a).unwrap(); // f32 -> u32 + let a_f32: Matrix2x3 = na::convert(a_u32); // u32 -> f32 + + assert_eq!(a, a_f32); + assert_eq!(expected, a_u32); +} + +#[test] +fn simple_transpose() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + let expected = Matrix3x2::new(1.0, 4.0, + 2.0, 5.0, + 3.0, 6.0); + + assert_eq!(a.transpose(), expected); +} + +#[test] +fn simple_transpose_mut() { + let mut a = Matrix3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0, + 7.0, 8.0, 9.0); + let expected = Matrix3::new(1.0, 4.0, 7.0, + 2.0, 5.0, 8.0, + 3.0, 6.0, 9.0); + + a.transpose_mut(); + assert_eq!(a, expected); +} + +#[test] +fn vector_index_mut() { + let mut v = Vector3::new(1, 2, 3); + + assert_eq!(v[0], 1); + assert_eq!(v[1], 2); + assert_eq!(v[2], 3); + + v[0] = 10; + v[1] = 20; + v[2] = 30; + + assert_eq!(v, Vector3::new(10, 20, 30)); +} + +#[test] +fn components_mut() { + let mut m2 = Matrix2::from_element(1.0); + let mut m3 = Matrix3::from_element(1.0); + let mut m4 = Matrix4::from_element(1.0); + let mut m5 = Matrix5::from_element(1.0); + let mut m6 = Matrix6::from_element(1.0); + + m2.m11 = 0.0; m2.m12 = 0.0; + m2.m21 = 0.0; m2.m22 = 0.0; + + m3.m11 = 0.0; m3.m12 = 0.0; m3.m13 = 0.0; + m3.m21 = 0.0; m3.m22 = 0.0; m3.m23 = 0.0; + m3.m31 = 0.0; m3.m32 = 0.0; m3.m33 = 0.0; + + m4.m11 = 0.0; m4.m12 = 0.0; m4.m13 = 0.0; m4.m14 = 0.0; + m4.m21 = 0.0; m4.m22 = 0.0; m4.m23 = 0.0; m4.m24 = 0.0; + m4.m31 = 0.0; m4.m32 = 0.0; m4.m33 = 0.0; m4.m34 = 0.0; + m4.m41 = 0.0; m4.m42 = 0.0; m4.m43 = 0.0; m4.m44 = 0.0; + + m5.m11 = 0.0; m5.m12 = 0.0; m5.m13 = 0.0; m5.m14 = 0.0; m5.m15 = 0.0; + m5.m21 = 0.0; m5.m22 = 0.0; m5.m23 = 0.0; m5.m24 = 0.0; m5.m25 = 0.0; + m5.m31 = 0.0; m5.m32 = 0.0; m5.m33 = 0.0; m5.m34 = 0.0; m5.m35 = 0.0; + m5.m41 = 0.0; m5.m42 = 0.0; m5.m43 = 0.0; m5.m44 = 0.0; m5.m45 = 0.0; + m5.m51 = 0.0; m5.m52 = 0.0; m5.m53 = 0.0; m5.m54 = 0.0; m5.m55 = 0.0; + + m6.m11 = 0.0; m6.m12 = 0.0; m6.m13 = 0.0; m6.m14 = 0.0; m6.m15 = 0.0; m6.m16 = 0.0; + m6.m21 = 0.0; m6.m22 = 0.0; m6.m23 = 0.0; m6.m24 = 0.0; m6.m25 = 0.0; m6.m26 = 0.0; + m6.m31 = 0.0; m6.m32 = 0.0; m6.m33 = 0.0; m6.m34 = 0.0; m6.m35 = 0.0; m6.m36 = 0.0; + m6.m41 = 0.0; m6.m42 = 0.0; m6.m43 = 0.0; m6.m44 = 0.0; m6.m45 = 0.0; m6.m46 = 0.0; + m6.m51 = 0.0; m6.m52 = 0.0; m6.m53 = 0.0; m6.m54 = 0.0; m6.m55 = 0.0; m6.m56 = 0.0; + m6.m61 = 0.0; m6.m62 = 0.0; m6.m63 = 0.0; m6.m64 = 0.0; m6.m65 = 0.0; m6.m66 = 0.0; + + assert!(m2.is_zero()); + assert!(m3.is_zero()); + assert!(m4.is_zero()); + assert!(m5.is_zero()); + assert!(m6.is_zero()); + + + let mut v1 = Vector1::from_element(1.0); + let mut v2 = Vector2::from_element(1.0); + let mut v3 = Vector3::from_element(1.0); + let mut v4 = Vector4::from_element(1.0); + let mut v5 = Vector5::from_element(1.0); + let mut v6 = Vector6::from_element(1.0); + + v1.x = 0.0; + v2.x = 0.0; v2.y = 0.0; + v3.x = 0.0; v3.y = 0.0; v3.z = 0.0; + v4.x = 0.0; v4.y = 0.0; v4.z = 0.0; v4.w = 0.0; + v5.x = 0.0; v5.y = 0.0; v5.z = 0.0; v5.w = 0.0; v5.a = 0.0; + v6.x = 0.0; v6.y = 0.0; v6.z = 0.0; v6.w = 0.0; v6.a = 0.0; v6.b = 0.0; + + assert!(v1.is_zero()); + assert!(v2.is_zero()); + assert!(v3.is_zero()); + assert!(v4.is_zero()); + assert!(v5.is_zero()); + assert!(v6.is_zero()); + + // Check that the components order is correct. + m3.m11 = 11.0; m3.m12 = 12.0; m3.m13 = 13.0; + m3.m21 = 21.0; m3.m22 = 22.0; m3.m23 = 23.0; + m3.m31 = 31.0; m3.m32 = 32.0; m3.m33 = 33.0; + + let expected_m3 = Matrix3::new(11.0, 12.0, 13.0, + 21.0, 22.0, 23.0, + 31.0, 32.0, 33.0); + assert_eq!(expected_m3, m3); +} + +#[cfg(feature = "arbitrary")] +quickcheck!{ + /* + * + * Transposition. + * + */ + fn transpose_transpose_is_self(m: Matrix2x3) -> bool { + m.transpose().transpose() == m + } + + fn transpose_mut_transpose_mut_is_self(m: Matrix3) -> bool { + let mut mm = m; + mm.transpose_mut(); + mm.transpose_mut(); + m == mm + } + + fn transpose_transpose_is_id_dyn(m: DMatrix) -> bool { + m.transpose().transpose() == m + } + + fn check_transpose_components_dyn(m: DMatrix) -> bool { + let tr = m.transpose(); + let (nrows, ncols) = m.shape(); + + if nrows != tr.shape().1 || ncols != tr.shape().0 { + return false + } + + for i in 0 .. nrows { + for j in 0 .. ncols { + if m[(i, j)] != tr[(j, i)] { + return false + } + } + } + + true + } + + fn tr_mul_is_transpose_then_mul(m: Matrix4x6, v: Vector4) -> bool { + m.transpose() * v == m.tr_mul(&v) + } + + /* + * + * + * Inversion. + * + * + */ + fn self_mul_inv_is_id_dim1(m: Matrix1) -> bool { + if let Some(im) = m.try_inverse() { + let id = Matrix1::one(); + relative_eq!(im * m, id, epsilon = 1.0e-7) && + relative_eq!(m * im, id, epsilon = 1.0e-7) + } + else { + true + } + } + + fn self_mul_inv_is_id_dim2(m: Matrix2) -> bool { + if let Some(im) = m.try_inverse() { + let id = Matrix2::one(); + relative_eq!(im * m, id, epsilon = 1.0e-7) && + relative_eq!(m * im, id, epsilon = 1.0e-7) + } + else { + true + } + } + + fn self_mul_inv_is_id_dim3(m: Matrix3) -> bool { + if let Some(im) = m.try_inverse() { + let id = Matrix3::one(); + relative_eq!(im * m, id, epsilon = 1.0e-7) && + relative_eq!(m * im, id, epsilon = 1.0e-7) + } + else { + true + } + } + + fn self_mul_inv_is_id_dim6(m: Matrix6) -> bool { + if let Some(im) = m.try_inverse() { + let id = Matrix6::one(); + relative_eq!(im * m, id, epsilon = 1.0e-7) && + relative_eq!(m * im, id, epsilon = 1.0e-7) + } + else { + true + } + } + + /* + * + * Normalization. + * + */ + fn normalized_vec_norm_is_one(v: Vector3) -> bool { + if let Some(nv) = v.try_normalize(1.0e-10) { + relative_eq!(nv.norm(), 1.0, epsilon = 1.0e-7) + } + else { + true + } + } + + fn normalized_vec_norm_is_one_dyn(v: DVector) -> bool { + if let Some(nv) = v.try_normalize(1.0e-10) { + relative_eq!(nv.norm(), 1.0) + } + else { + true + } + } +} + +// FIXME: move this to alga ? +macro_rules! finite_dim_inner_space_test( + ($($Vector: ident, $orthonormal_subspace: ident, $orthonormalization: ident);* $(;)*) => {$( + #[cfg(feature = "arbitrary")] + quickcheck!{ + fn $orthonormal_subspace(vs: Vec<$Vector>) -> bool { + let mut given_basis = vs.clone(); + let given_basis_dim = $Vector::orthonormalize(&mut given_basis[..]); + let mut ortho_basis = Vec::new(); + $Vector::orthonormal_subspace_basis( + &given_basis[.. given_basis_dim], + |e| { ortho_basis.push(*e); true } + ); + + if !is_subspace_basis(&ortho_basis[..]) { + return false; + } + + for v in vs { + for b in &ortho_basis { + if !relative_eq!(v.dot(b), 0.0, epsilon = 1.0e-7) { + println!("Found dot product: {} · {} = {}", v, b, v.dot(b)); + return false; + } + } + } + + true + } + + fn $orthonormalization(vs: Vec<$Vector>) -> bool { + let mut basis = vs.clone(); + let subdim = $Vector::orthonormalize(&mut basis[..]); + + if !is_subspace_basis(&basis[.. subdim]) { + return false; + } + + for mut e in vs { + for b in &basis[.. subdim] { + e -= e.dot(b) * b + } + + // Any element of `e` must be a linear combination of the basis elements. + if !relative_eq!(e.norm(), 0.0, epsilon = 1.0e-7) { + println!("Orthonormalization; element decomposition failure: {}", e); + println!("... the non-zero norm is: {}", e.norm()); + return false; + } + } + + true + } + } + )*} +); + +finite_dim_inner_space_test!( + Vector1, orthonormal_subspace_basis1, orthonormalize1; + Vector2, orthonormal_subspace_basis2, orthonormalize2; + Vector3, orthonormal_subspace_basis3, orthonormalize3; + Vector4, orthonormal_subspace_basis4, orthonormalize4; + Vector5, orthonormal_subspace_basis5, orthonormalize5; + Vector6, orthonormal_subspace_basis6, orthonormalize6; +); + +/* + * + * Helper functions. + * + */ +fn is_subspace_basis + Display>(vs: &[T]) -> bool { + for i in 0 .. vs.len() { + // Basis elements must be normalized. + if !relative_eq!(vs[i].norm(), 1.0, epsilon = 1.0e-7) { + println!("Non-zero basis element norm: {}", vs[i].norm()); + return false; + } + + for j in 0 .. i { + // Basis elements must be orthogonal. + if !relative_eq!(vs[i].dot(&vs[j]), 0.0, epsilon = 1.0e-7) { + println!("Non-orthogonal basis elements: {} · {} = {}", vs[i], vs[j], vs[i].dot(&vs[j])); + return false + } + } + } + + true +} diff --git a/tests/matrix_slice.rs b/tests/matrix_slice.rs new file mode 100644 index 00000000..10bdc9f4 --- /dev/null +++ b/tests/matrix_slice.rs @@ -0,0 +1,162 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate nalgebra as na; + +use na::{U2, U3, U4}; +use na::{DMatrix, + Matrix2, Matrix3, + Matrix3x4, Matrix4x2, Matrix2x4, Matrix6x2, Matrix2x6}; + +#[test] +fn nested_fixed_slices() { + let a = Matrix3x4::new(11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0); + + let s1 = a.fixed_slice::(0, 1); // Simple slice. + let s2 = s1.fixed_slice::(1, 1); // Slice of slice. + let s3 = s1.fixed_slice_with_steps::((0, 0), (2, 2)); // Slice of slice with steps. + + let expected_owned_s1 = Matrix3::new(12.0, 13.0, 14.0, + 22.0, 23.0, 24.0, + 32.0, 33.0, 34.0); + + let expected_owned_s2 = Matrix2::new(23.0, 24.0, + 33.0, 34.0); + + let expected_owned_s3 = Matrix2::new(12.0, 14.0, + 32.0, 34.0); + + assert_eq!(expected_owned_s1, s1.clone_owned()); + assert_eq!(expected_owned_s2, s2.clone_owned()); + assert_eq!(expected_owned_s3, s3.clone_owned()); +} + +#[test] +fn nested_slices() { + let a = Matrix3x4::new(11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0); + + let s1 = a.slice((0, 1), (3, 3)); + let s2 = s1.slice((1, 1), (2, 2)); + let s3 = s1.slice_with_steps((0, 0), (2, 2), (2, 2)); + + let expected_owned_s1 = DMatrix::from_row_slice(3, 3, &[ 12.0, 13.0, 14.0, + 22.0, 23.0, 24.0, + 32.0, 33.0, 34.0 ]); + + let expected_owned_s2 = DMatrix::from_row_slice(2, 2, &[ 23.0, 24.0, + 33.0, 34.0 ]); + + let expected_owned_s3 = DMatrix::from_row_slice(2, 2, &[ 12.0, 14.0, + 32.0, 34.0 ]); + + assert_eq!(expected_owned_s1, s1.clone_owned()); + assert_eq!(expected_owned_s2, s2.clone_owned()); + assert_eq!(expected_owned_s3, s3.clone_owned()); +} + +#[test] +fn slice_mut() { + let mut a = Matrix3x4::new(11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0); + + { + // We modify `a` through the mutable slice. + let mut s1 = a.slice_with_steps_mut((0, 1), (2, 2), (2, 2)); + s1.fill(0.0); + } + + let expected_a = Matrix3x4::new(11.0, 0.0, 13.0, 0.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 0.0, 33.0, 0.0); + + assert_eq!(expected_a, a); +} + +#[test] +fn nested_row_slices() { + let a = Matrix6x2::new(11.0, 12.0, + 21.0, 22.0, + 31.0, 32.0, + 41.0, 42.0, + 51.0, 52.0, + 61.0, 62.0); + let s1 = a.fixed_rows::(1); + let s2 = s1.fixed_rows_with_step::(1, 2); + + let expected_owned_s1 = Matrix4x2::new(21.0, 22.0, + 31.0, 32.0, + 41.0, 42.0, + 51.0, 52.0); + + let expected_owned_s2 = Matrix2::new(31.0, 32.0, + 51.0, 52.0); + + assert_eq!(expected_owned_s1, s1.clone_owned()); + assert_eq!(expected_owned_s2, s2.clone_owned()); +} + +#[test] +fn row_slice_mut() { + let mut a = Matrix6x2::new(11.0, 12.0, + 21.0, 22.0, + 31.0, 32.0, + 41.0, 42.0, + 51.0, 52.0, + 61.0, 62.0); + { + // We modify `a` through the mutable slice. + let mut s1 = a.rows_with_step_mut(1, 3, 2); + s1.fill(0.0); + } + + let expected_a = Matrix6x2::new(11.0, 12.0, + 0.0, 0.0, + 31.0, 32.0, + 0.0, 0.0, + 51.0, 52.0, + 0.0, 0.0); + + assert_eq!(expected_a, a); +} + +#[test] +fn nested_col_slices() { + let a = Matrix2x6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 21.0, 22.0, 23.0, 24.0, 25.0, 26.0); + let s1 = a.fixed_columns::(1); + let s2 = s1.fixed_columns_with_step::(1, 2); + + let expected_owned_s1 = Matrix2x4::new(12.0, 13.0, 14.0, 15.0, + 22.0, 23.0, 24.0, 25.0); + + let expected_owned_s2 = Matrix2::new(13.0, 15.0, + 23.0, 25.0); + + assert_eq!(expected_owned_s1, s1.clone_owned()); + assert_eq!(expected_owned_s2, s2.clone_owned()); +} + +#[test] +fn col_slice_mut() { + let mut a = Matrix2x6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 21.0, 22.0, 23.0, 24.0, 25.0, 26.0); + + { + // We modify `a` through the mutable slice. + let mut s1 = a.columns_with_step_mut(1, 3, 2); + s1.fill(0.0); + } + + let expected_a = Matrix2x6::new(11.0, 0.0, 13.0, 0.0, 15.0, 0.0, + 21.0, 0.0, 23.0, 0.0, 25.0, 0.0); + + assert_eq!(expected_a, a.clone_owned()); +} diff --git a/tests/op_assign.rs b/tests/op_assign.rs deleted file mode 100644 index 970d682c..00000000 --- a/tests/op_assign.rs +++ /dev/null @@ -1,76 +0,0 @@ -extern crate nalgebra as na; -extern crate rand; - -use std::ops::{Mul, Div, Add, Sub, MulAssign, DivAssign, AddAssign, SubAssign}; -use rand::random; -use na::{Point3, Vector3, Matrix3, Rotation3, Isometry3, Similarity3, Quaternion, UnitQuaternion}; - -// NOTE: we test only the 3D version because the others share the same code anyway. - -macro_rules! test_op_vs_op_assign( - ($name: ident, $t1: ty, $t2: ty, $op: ident, $op_assign: ident) => ( - #[test] - fn $name() { - for _ in 0usize .. 10000 { - let rand1 = random::<$t1>(); - let rand2 = random::<$t2>(); - let mut res = rand1; - - res.$op_assign(rand2); - - assert_eq!(rand1.$op(rand2), res) - } - } - ) -); - -// Multiplication. -test_op_vs_op_assign!(test_vec3_f32_mul_assign, Vector3, f32, mul, mul_assign); -test_op_vs_op_assign!(test_mat3_f32_mul_assign, Matrix3, f32, mul, mul_assign); -test_op_vs_op_assign!(test_quaternion_f32_mul_assign, Quaternion, f32, mul, mul_assign); - -test_op_vs_op_assign!(test_vec3_vec3_mul_assign, Vector3, Vector3, mul, mul_assign); -test_op_vs_op_assign!(test_quaternion_quaternion_mul_assign, Quaternion, Quaternion, mul, mul_assign); -test_op_vs_op_assign!(test_unit_quaternion_unit_quaternion_mul_assign, UnitQuaternion, UnitQuaternion, mul, mul_assign); - -test_op_vs_op_assign!(test_vec3_unit_quaternion_mul_assign, Vector3, UnitQuaternion, mul, mul_assign); -test_op_vs_op_assign!(test_point3_unit_quaternion_mul_assign, Point3, UnitQuaternion, mul, mul_assign); - -test_op_vs_op_assign!(test_mat3_mat3_mul_assign, Matrix3, Matrix3, mul, mul_assign); -test_op_vs_op_assign!(test_vec3_mat3_mul_assign, Vector3, Matrix3, mul, mul_assign); -test_op_vs_op_assign!(test_point3_mat3_mul_assign, Point3, Matrix3, mul, mul_assign); - -test_op_vs_op_assign!(test_rot3_rot3_mul_assign, Rotation3, Rotation3, mul, mul_assign); -test_op_vs_op_assign!(test_vec3_rot3_mul_assign, Vector3, Rotation3, mul, mul_assign); -test_op_vs_op_assign!(test_point3_rot3_mul_assign, Point3, Rotation3, mul, mul_assign); - -test_op_vs_op_assign!(test_iso3_iso3_mul_assign, Isometry3, Isometry3, mul, mul_assign); -test_op_vs_op_assign!(test_iso3_rot3_mul_assign, Isometry3, Rotation3, mul, mul_assign); - -test_op_vs_op_assign!(test_sim3_sim3_mul_assign, Similarity3, Similarity3, mul, mul_assign); -test_op_vs_op_assign!(test_sim3_iso3_mul_assign, Similarity3, Isometry3, mul, mul_assign); -test_op_vs_op_assign!(test_sim3_rot3_mul_assign, Similarity3, Rotation3, mul, mul_assign); - -// Division. -test_op_vs_op_assign!(test_vec3_vec3_div_assign, Vector3, Vector3, div, div_assign); -test_op_vs_op_assign!(test_quaternion_quaternion_div_assign, Quaternion, Quaternion, div, div_assign); -test_op_vs_op_assign!(test_unit_quaternion_unit_quaternion_div_assign, UnitQuaternion, UnitQuaternion, div, div_assign); - -test_op_vs_op_assign!(test_vec3_f32_div_assign, Vector3, f32, div, div_assign); -test_op_vs_op_assign!(test_mat3_f32_div_assign, Matrix3, f32, div, div_assign); - -// Addition. -test_op_vs_op_assign!(test_vec3_vec3_add_assign, Vector3, Vector3, add, add_assign); -test_op_vs_op_assign!(test_mat3_mat3_add_assign, Matrix3, Matrix3, add, add_assign); -test_op_vs_op_assign!(test_quaternion_quaternion_add_assign, Quaternion, Quaternion, add, add_assign); - -test_op_vs_op_assign!(test_vec3_f32_add_assign, Vector3, f32, add, add_assign); -test_op_vs_op_assign!(test_mat3_f32_add_assign, Matrix3, f32, add, add_assign); - -// Subtraction. -test_op_vs_op_assign!(test_vec3_vec3_sub_assign, Vector3, Vector3, sub, sub_assign); -test_op_vs_op_assign!(test_mat3_mat3_sub_assign, Matrix3, Matrix3, sub, sub_assign); -test_op_vs_op_assign!(test_quaternion_quaternion_sub_assign, Quaternion, Quaternion, sub, sub_assign); - -test_op_vs_op_assign!(test_vec3_f32_sub_assign, Vector3, f32, sub, sub_assign); -test_op_vs_op_assign!(test_mat3_f32_sub_assign, Matrix3, f32, sub, sub_assign); diff --git a/tests/point.rs b/tests/point.rs new file mode 100644 index 00000000..05fb9f23 --- /dev/null +++ b/tests/point.rs @@ -0,0 +1,104 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use num::Zero; +use na::{Point3, Vector3, Vector4}; + +#[test] +fn point_ops() { + let a = Point3::new(1.0, 2.0, 3.0); + let b = Point3::new(1.0, 2.0, 3.0); + let c = Vector3::new(1.0, 2.0, 3.0); + + assert_eq!( a - b, Vector3::zero()); + assert_eq!(&a - &b, Vector3::zero()); + assert_eq!( a - &b, Vector3::zero()); + assert_eq!(&a - b, Vector3::zero()); + + assert_eq!( b - c, Point3::origin()); + assert_eq!(&b - &c, Point3::origin()); + assert_eq!( b - &c, Point3::origin()); + assert_eq!(&b - c, Point3::origin()); + + assert_eq!( b + c, 2.0 * a); + assert_eq!(&b + &c, 2.0 * a); + assert_eq!( b + &c, 2.0 * a); + assert_eq!(&b + c, 2.0 * a); + + let mut a1 = a; + let mut a2 = a; + let mut a3 = a; + let mut a4 = a; + + a1 -= c; + a2 -= &c; + a3 += c; + a4 += &c; + + assert_eq!(a1, Point3::origin()); + assert_eq!(a2, Point3::origin()); + assert_eq!(a3, 2.0 * a); + assert_eq!(a4, 2.0 * a); +} + +#[test] +fn point_coordinates() { + let mut pt = Point3::origin(); + + assert_eq!(pt.x, 0); + assert_eq!(pt.y, 0); + assert_eq!(pt.z, 0); + + pt.x = 1; + pt.y = 2; + pt.z = 3; + + assert_eq!(pt.x, 1); + assert_eq!(pt.y, 2); + assert_eq!(pt.z, 3); +} + +#[test] +fn point_scale() { + let pt = Point3::new(1, 2, 3); + let expected = Point3::new(10, 20, 30); + + assert_eq!(pt * 10, expected); + assert_eq!(&pt * 10, expected); + assert_eq!(10 * pt, expected); + assert_eq!(10 * &pt, expected); + +} + +#[test] +fn point_vector_sum() { + let pt = Point3::new(1, 2, 3); + let vec = Vector3::new(10, 20, 30); + let expected = Point3::new(11, 22, 33); + + assert_eq!(&pt + &vec, expected); + assert_eq!(pt + &vec, expected); + assert_eq!(&pt + vec, expected); + assert_eq!(pt + vec, expected); +} + +#[test] +fn to_homogeneous() { + let a = Point3::new(1.0, 2.0, 3.0); + let expected = Vector4::new(1.0, 2.0, 3.0, 1.0); + + assert_eq!(a.to_homogeneous(), expected); +} + +quickcheck!( + fn point_sub(pt1: Point3, pt2: Point3) -> bool { + let dpt = &pt2 - &pt1; + relative_eq!(pt2, pt1 + dpt, epsilon = 1.0e-7) + } +); diff --git a/tests/quat.rs b/tests/quat.rs deleted file mode 100644 index 9912f057..00000000 --- a/tests/quat.rs +++ /dev/null @@ -1,121 +0,0 @@ -extern crate nalgebra as na; -extern crate rand; - -use na::{Point3, Quaternion, Vector3, Rotation3, UnitQuaternion, Rotation, one}; -use rand::random; - -#[test] -fn test_quaternion_as_matrix() { - for _ in 0usize .. 10000 { - let axis_angle: Vector3 = random(); - - assert!(na::approx_eq(&UnitQuaternion::from_scaled_axis(axis_angle).to_rotation_matrix(), &Rotation3::new(axis_angle))) - } -} - -#[test] -fn test_quaternion_mul_vec_or_point_as_matrix() { - for _ in 0usize .. 10000 { - let axis_angle: Vector3 = random(); - let vector: Vector3 = random(); - let point: Point3 = random(); - - let matrix = Rotation3::new(axis_angle); - let quaternion = UnitQuaternion::from_scaled_axis(axis_angle); - - assert!(na::approx_eq(&(matrix * vector), &(quaternion * vector))); - assert!(na::approx_eq(&(matrix * point), &(quaternion * point))); - assert!(na::approx_eq(&(vector * matrix), &(vector * quaternion))); - assert!(na::approx_eq(&(point * matrix), &(point * quaternion))); - } -} - -#[test] -fn test_quaternion_div_quaternion() { - for _ in 0usize .. 10000 { - let axis_angle1: Vector3 = random(); - let axis_angle2: Vector3 = random(); - - let r1 = Rotation3::new(axis_angle1); - let r2 = na::inverse(&Rotation3::new(axis_angle2)).unwrap(); - - let q1 = UnitQuaternion::from_scaled_axis(axis_angle1); - let q2 = UnitQuaternion::from_scaled_axis(axis_angle2); - - assert!(na::approx_eq(&(q1 / q2).to_rotation_matrix(), &(r1 * r2))) - } -} - -#[test] -fn test_quaternion_to_axis_angle() { - for _ in 0usize .. 10000 { - let axis_angle: Vector3 = random(); - - let q = UnitQuaternion::from_scaled_axis(axis_angle); - - println!("{:?} {:?}", q.rotation(), axis_angle); - assert!(na::approx_eq(&q.rotation(), &axis_angle)) - } -} - -#[test] -fn test_quaternion_euler_angles() { - for _ in 0usize .. 10000 { - let angles: Vector3 = random(); - - let q = UnitQuaternion::from_euler_angles(angles.x, angles.y, angles.z); - let m = Rotation3::from_euler_angles(angles.x, angles.y, angles.z); - - assert!(na::approx_eq(&q.to_rotation_matrix(), &m)) - } -} - -#[test] -fn test_quaternion_rotation_between() { - let q1: UnitQuaternion = random(); - let q2: UnitQuaternion = random(); - - let delta = na::rotation_between(&q1, &q2); - - assert!(na::approx_eq(&(delta * q1), &q2)) -} - -#[test] -fn test_quaternion_angle_between() { - let q1: UnitQuaternion = random(); - let q2: UnitQuaternion = random(); - - let delta = na::rotation_between(&q1, &q2); - let delta_angle = na::angle_between(&q1, &q2); - - assert!(na::approx_eq(&na::norm(&na::rotation(&delta)), &delta_angle)) -} - -#[test] -fn test_quaternion_exp_zero_is_one() { - let q = Quaternion::new(0., 0., 0., 0.); - assert!(na::approx_eq(&q.exp(), &one())) -} - -#[test] -fn test_quaternion_neutral() { - for _ in 0 .. 10000 { - let q1: Quaternion = random(); - let qi: Quaternion = one(); - let q2 = q1 * qi; - let q3 = qi * q1; - - assert!(na::approx_eq(&q1, &q2) && na::approx_eq(&q2, &q3)) - } -} - -#[test] -fn test_quaternion_polar_decomposition() { - for _ in 0 .. 10000 { - let q1: Quaternion = random(); - let decomp = q1.polar_decomposition(); - let q2 = Quaternion::from_polar_decomposition(decomp.0, decomp.1, decomp.2); - - assert!(na::approx_eq(&q1, &q2)) - } -} diff --git a/tests/quaternion.rs b/tests/quaternion.rs new file mode 100644 index 00000000..bcd5de64 --- /dev/null +++ b/tests/quaternion.rs @@ -0,0 +1,247 @@ +#![allow(non_snake_case)] + +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use na::{Unit, UnitQuaternion, Quaternion, Vector3, Point3, Rotation3}; + + +quickcheck!( + + /* + * + * From/to rotation matrix. + * + */ + fn unit_quaternion_rotation_conversion(q: UnitQuaternion) -> bool { + let r = q.to_rotation_matrix(); + let qq = UnitQuaternion::from_rotation_matrix(&r); + let rr = qq.to_rotation_matrix(); + + relative_eq!(q, qq, epsilon = 1.0e-7) && + relative_eq!(r, rr, epsilon = 1.0e-7) + } + + /* + * + * PointBase/Vector transformation. + * + */ + fn unit_quaternion_transformation(q: UnitQuaternion, v: Vector3, p: Point3) -> bool { + let r = q.to_rotation_matrix(); + let rv = r * v; + let rp = r * p; + + relative_eq!( q * v, rv, epsilon = 1.0e-7) && + relative_eq!( q * &v, rv, epsilon = 1.0e-7) && + relative_eq!(&q * v, rv, epsilon = 1.0e-7) && + relative_eq!(&q * &v, rv, epsilon = 1.0e-7) && + + relative_eq!( q * p, rp, epsilon = 1.0e-7) && + relative_eq!( q * &p, rp, epsilon = 1.0e-7) && + relative_eq!(&q * p, rp, epsilon = 1.0e-7) && + relative_eq!(&q * &p, rp, epsilon = 1.0e-7) + } + + + /* + * + * Inversion. + * + */ + fn unit_quaternion_inv(q: UnitQuaternion) -> bool { + let iq = q.inverse(); + relative_eq!(&iq * &q, UnitQuaternion::identity(), epsilon = 1.0e-7) && + relative_eq!( iq * &q, UnitQuaternion::identity(), epsilon = 1.0e-7) && + relative_eq!(&iq * q, UnitQuaternion::identity(), epsilon = 1.0e-7) && + relative_eq!( iq * q, UnitQuaternion::identity(), epsilon = 1.0e-7) && + + relative_eq!(&q * &iq, UnitQuaternion::identity(), epsilon = 1.0e-7) && + relative_eq!( q * &iq, UnitQuaternion::identity(), epsilon = 1.0e-7) && + relative_eq!(&q * iq, UnitQuaternion::identity(), epsilon = 1.0e-7) && + relative_eq!( q * iq, UnitQuaternion::identity(), epsilon = 1.0e-7) + } + + /* + * + * Quaterion * Vector == RotationBase * Vector + * + */ + fn unit_quaternion_mul_vector(q: UnitQuaternion, v: Vector3, p: Point3) -> bool { + let r = q.to_rotation_matrix(); + + relative_eq!(q * v, r * v, epsilon = 1.0e-7) && + relative_eq!(q * p, r * p, epsilon = 1.0e-7) && + // Equivalence q = -q + relative_eq!((-q) * v, r * v, epsilon = 1.0e-7) && + relative_eq!((-q) * p, r * p, epsilon = 1.0e-7) + } + + /* + * + * Unit quaternion double-covering. + * + */ + fn unit_quaternion_double_covering(q: UnitQuaternion) -> bool { + let mq = -q; + + mq == q && mq.angle() == q.angle() && mq.axis() == q.axis() + } + + // Test that all operators (incl. all combinations of references) work. + // See the top comment on `geometry/quaternion_ops.rs` for details on which operations are + // supported. + fn all_op_exist(q: Quaternion, uq: UnitQuaternion, + v: Vector3, p: Point3, r: Rotation3, + s: f64) -> bool { + let uv = Unit::new(v); + + let qpq = q + q; + let qmq = q - q; + let qMq = q * q; + let mq = -q; + let qMs = q * s; + let qDs = q / s; + let sMq = s * q; + + let uqMuq = uq * uq; + let uqMr = uq * r; + let rMuq = r * uq; + let uqDuq = uq / uq; + let uqDr = uq / r; + let rDuq = r / uq; + + let uqMp = uq * p; + let uqMv = uq * v; + let uqMuv = uq * uv; + + let mut qMs1 = q; + + let mut qMq1 = q; + let mut qMq2 = q; + + let mut qpq1 = q; + let mut qpq2 = q; + + let mut qmq1 = q; + let mut qmq2 = q; + + let mut uqMuq1 = uq; + let mut uqMuq2 = uq; + + let mut uqMr1 = uq; + let mut uqMr2 = uq; + + let mut uqDuq1 = uq; + let mut uqDuq2 = uq; + + let mut uqDr1 = uq; + let mut uqDr2 = uq; + + qMs1 *= s; + + qMq1 *= q; + qMq2 *= &q; + + qpq1 += q; + qpq2 += &q; + + qmq1 -= q; + qmq2 -= &q; + + uqMuq1 *= uq; + uqMuq2 *= &uq; + + uqMr1 *= r; + uqMr2 *= &r; + + uqDuq1 /= uq; + uqDuq2 /= &uq; + + uqDr1 /= r; + uqDr2 /= &r; + + qMs1 == qMs && + + qMq1 == qMq && + qMq1 == qMq2 && + + qpq1 == qpq && + qpq1 == qpq2 && + + qmq1 == qmq && + qmq1 == qmq2 && + + uqMuq1 == uqMuq && + uqMuq1 == uqMuq2 && + + uqMr1 == uqMr && + uqMr1 == uqMr2 && + + uqDuq1 == uqDuq && + uqDuq1 == uqDuq2 && + + uqDr1 == uqDr && + uqDr1 == uqDr2 && + + qpq == &q + &q && + qpq == q + &q && + qpq == &q + q && + + qmq == &q - &q && + qmq == q - &q && + qmq == &q - q && + + qMq == &q * &q && + qMq == q * &q && + qMq == &q * q && + + mq == -&q && + + qMs == &q * s && + qDs == &q / s && + sMq == s * &q && + + uqMuq == &uq * &uq && + uqMuq == uq * &uq && + uqMuq == &uq * uq && + + uqMr == &uq * &r && + uqMr == uq * &r && + uqMr == &uq * r && + + rMuq == &r * &uq && + rMuq == r * &uq && + rMuq == &r * uq && + + uqDuq == &uq / &uq && + uqDuq == uq / &uq && + uqDuq == &uq / uq && + + uqDr == &uq / &r && + uqDr == uq / &r && + uqDr == &uq / r && + + rDuq == &r / &uq && + rDuq == r / &uq && + rDuq == &r / uq && + + uqMp == &uq * &p && + uqMp == uq * &p && + uqMp == &uq * p && + + uqMv == &uq * &v && + uqMv == uq * &v && + uqMv == &uq * v && + + uqMuv == &uq * &uv && + uqMuv == uq * &uv && + uqMuv == &uq * uv + } +); diff --git a/tests/rotation.rs b/tests/rotation.rs new file mode 100644 index 00000000..2a2e0af3 --- /dev/null +++ b/tests/rotation.rs @@ -0,0 +1,180 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use alga::general::Real; +use na::{Vector2, Vector3, Rotation2, Rotation3, Unit}; + +#[test] +fn angle_2() { + let a = Vector2::new(4.0, 0.0); + let b = Vector2::new(9.0, 0.0); + + assert_eq!(a.angle(&b), 0.0); +} + +#[test] +fn angle_3() { + let a = Vector3::new(4.0, 0.0, 0.5); + let b = Vector3::new(8.0, 0.0, 1.0); + + assert_eq!(a.angle(&b), 0.0); +} + +quickcheck!( + /* + * + * Inversion is transposition. + * + */ + fn rotation_inv_3(a: Rotation3) -> bool { + let ta = a.transpose(); + let ia = a.inverse(); + + ta == ia && + relative_eq!(&ta * &a, Rotation3::identity(), epsilon = 1.0e-7) && + relative_eq!(&ia * a, Rotation3::identity(), epsilon = 1.0e-7) && + relative_eq!( a * &ta, Rotation3::identity(), epsilon = 1.0e-7) && + relative_eq!( a * ia, Rotation3::identity(), epsilon = 1.0e-7) + } + + fn rotation_inv_2(a: Rotation2) -> bool { + let ta = a.transpose(); + let ia = a.inverse(); + + ta == ia && + relative_eq!(&ta * &a, Rotation2::identity(), epsilon = 1.0e-7) && + relative_eq!(&ia * a, Rotation2::identity(), epsilon = 1.0e-7) && + relative_eq!( a * &ta, Rotation2::identity(), epsilon = 1.0e-7) && + relative_eq!( a * ia, Rotation2::identity(), epsilon = 1.0e-7) + } + + /* + * + * Angle between vectors. + * + */ + fn angle_is_commutative_2(a: Vector2, b: Vector2) -> bool { + a.angle(&b) == b.angle(&a) + } + + fn angle_is_commutative_3(a: Vector3, b: Vector3) -> bool { + a.angle(&b) == b.angle(&a) + } + + + /* + * + * RotationBase matrix between vectors. + * + */ + fn rotation_between_is_anticommutative_2(a: Vector2, b: Vector2) -> bool { + let rab = Rotation2::rotation_between(&a, &b); + let rba = Rotation2::rotation_between(&b, &a); + + relative_eq!(rab * rba, Rotation2::identity()) + } + + fn rotation_between_is_anticommutative_3(a: Vector3, b: Vector3) -> bool { + let rots = (Rotation3::rotation_between(&a, &b), Rotation3::rotation_between(&b, &a)); + if let (Some(rab), Some(rba)) = rots { + relative_eq!(rab * rba, Rotation3::identity(), epsilon = 1.0e-7) + } + else { + true + } + } + + fn rotation_between_is_identity(v2: Vector2, v3: Vector3) -> bool { + let vv2 = 3.42 * v2; + let vv3 = 4.23 * v3; + + relative_eq!(v2.angle(&vv2), 0.0, epsilon = 1.0e-7) && + relative_eq!(v3.angle(&vv3), 0.0, epsilon = 1.0e-7) && + relative_eq!(Rotation2::rotation_between(&v2, &vv2), Rotation2::identity()) && + Rotation3::rotation_between(&v3, &vv3).unwrap() == Rotation3::identity() + } + + fn rotation_between_2(a: Vector2, b: Vector2) -> bool { + if !relative_eq!(a.angle(&b), 0.0, epsilon = 1.0e-7) { + let r = Rotation2::rotation_between(&a, &b); + relative_eq!((r * a).angle(&b), 0.0, epsilon = 1.0e-7) + } + else { + true + } + } + + fn rotation_between_3(a: Vector3, b: Vector3) -> bool { + if !relative_eq!(a.angle(&b), 0.0, epsilon = 1.0e-7) { + let r = Rotation3::rotation_between(&a, &b).unwrap(); + relative_eq!((r * a).angle(&b), 0.0, epsilon = 1.0e-7) + } + else { + true + } + } + + + /* + * + * RotationBase construction. + * + */ + fn new_rotation_2(angle: f64) -> bool { + let r = Rotation2::new(angle); + + let angle = na::wrap(angle, -f64::pi(), f64::pi()); + relative_eq!(r.angle(), angle, epsilon = 1.0e-7) + } + + fn new_rotation_3(axisangle: Vector3) -> bool { + let r = Rotation3::new(axisangle); + + if let Some((axis, angle)) = Unit::try_new_and_get(axisangle, 0.0) { + let angle = na::wrap(angle, -f64::pi(), f64::pi()); + (relative_eq!(r.angle(), angle, epsilon = 1.0e-7) && + relative_eq!(r.axis().unwrap(), axis, epsilon = 1.0e-7)) || + (relative_eq!(r.angle(), -angle, epsilon = 1.0e-7) && + relative_eq!(r.axis().unwrap(), -axis, epsilon = 1.0e-7)) + } + else { + r == Rotation3::identity() + } + } + + /* + * + * RotationBase pow. + * + */ + fn powf_rotation_2(angle: f64, pow: f64) -> bool { + let r = Rotation2::new(angle).powf(pow); + + let angle = na::wrap(angle, -f64::pi(), f64::pi()); + let pangle = na::wrap(angle * pow, -f64::pi(), f64::pi()); + relative_eq!(r.angle(), pangle, epsilon = 1.0e-7) + } + + fn powf_rotation_3(axisangle: Vector3, pow: f64) -> bool { + let r = Rotation3::new(axisangle).powf(pow); + + if let Some((axis, angle)) = Unit::try_new_and_get(axisangle, 0.0) { + let angle = na::wrap(angle, -f64::pi(), f64::pi()); + let pangle = na::wrap(angle * pow, -f64::pi(), f64::pi()); + + (relative_eq!(r.angle(), pangle, epsilon = 1.0e-7) && + relative_eq!(r.axis().unwrap(), axis, epsilon = 1.0e-7)) || + (relative_eq!(r.angle(), -pangle, epsilon = 1.0e-7) && + relative_eq!(r.axis().unwrap(), -axis, epsilon = 1.0e-7)) + } + else { + r == Rotation3::identity() + } + } +); diff --git a/tests/similarity.rs b/tests/similarity.rs new file mode 100644 index 00000000..c827eda8 --- /dev/null +++ b/tests/similarity.rs @@ -0,0 +1,274 @@ +#![allow(non_snake_case)] + +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate alga; +extern crate nalgebra as na; + +use alga::linear::{Transformation, ProjectiveTransformation}; +use na::{Vector3, Point3, Similarity3, Translation3, Isometry3, UnitQuaternion}; + +quickcheck!( + fn inverse_is_identity(i: Similarity3, p: Point3, v: Vector3) -> bool { + let ii = i.inverse(); + + relative_eq!(i * ii, Similarity3::identity(), epsilon = 1.0e-7) && + relative_eq!(ii * i, Similarity3::identity(), epsilon = 1.0e-7) && + relative_eq!((i * ii) * p, p, epsilon = 1.0e-7) && + relative_eq!((ii * i) * p, p, epsilon = 1.0e-7) && + relative_eq!((i * ii) * v, v, epsilon = 1.0e-7) && + relative_eq!((ii * i) * v, v, epsilon = 1.0e-7) + } + + fn inverse_is_parts_inversion(t: Translation3, r: UnitQuaternion, scaling: f64) -> bool { + if relative_eq!(scaling, 0.0) { + true + } + else { + let s = Similarity3::from_isometry(t * r, scaling); + s.inverse() == Similarity3::from_scaling(1.0 / scaling) * r.inverse() * t.inverse() + } + } + + fn multiply_equals_alga_transform(s: Similarity3, v: Vector3, p: Point3) -> bool { + s * v == s.transform_vector(&v) && + s * p == s.transform_point(&p) && + relative_eq!(s.inverse() * v, s.inverse_transform_vector(&v), epsilon = 1.0e-7) && + relative_eq!(s.inverse() * p, s.inverse_transform_point(&p), epsilon = 1.0e-7) + } + + fn composition(i: Isometry3, uq: UnitQuaternion, + t: Translation3, v: Vector3, p: Point3, scaling: f64) -> bool { + if relative_eq!(scaling, 0.0) { + return true; + } + + let s = Similarity3::from_scaling(scaling); + + // (rotation × translation × scaling) × point = rotation × (translation × (scaling × point)) + relative_eq!((uq * t * s) * v, uq * (scaling * v), epsilon = 1.0e-7) && + relative_eq!((uq * t * s) * p, uq * (t * (scaling * p)), epsilon = 1.0e-7) && + + // (translation × rotation × scaling) × point = translation × (rotation × (scaling × point)) + relative_eq!((t * uq * s) * v, uq * (scaling * v), epsilon = 1.0e-7) && + relative_eq!((t * uq * s) * p, t * (uq * (scaling * p)), epsilon = 1.0e-7) && + + // (rotation × isometry × scaling) × point = rotation × (isometry × (scaling × point)) + relative_eq!((uq * i * s) * v, uq * (i * (scaling * v)), epsilon = 1.0e-7) && + relative_eq!((uq * i * s) * p, uq * (i * (scaling * p)), epsilon = 1.0e-7) && + + // (isometry × rotation × scaling) × point = isometry × (rotation × (scaling × point)) + relative_eq!((i * uq * s) * v, i * (uq * (scaling * v)), epsilon = 1.0e-7) && + relative_eq!((i * uq * s) * p, i * (uq * (scaling * p)), epsilon = 1.0e-7) && + + // (translation × isometry × scaling) × point = translation × (isometry × (scaling × point)) + relative_eq!((t * i * s) * v, (i * (scaling * v)), epsilon = 1.0e-7) && + relative_eq!((t * i * s) * p, t * (i * (scaling * p)), epsilon = 1.0e-7) && + + // (isometry × translation × scaling) × point = isometry × (translation × (scaling × point)) + relative_eq!((i * t * s) * v, i * (scaling * v), epsilon = 1.0e-7) && + relative_eq!((i * t * s) * p, i * (t * (scaling * p)), epsilon = 1.0e-7) && + + + /* + * Same as before but with scaling on the middle. + */ + // (rotation × scaling × translation) × point = rotation × (scaling × (translation × point)) + relative_eq!((uq * s * t) * v, uq * (scaling * v), epsilon = 1.0e-7) && + relative_eq!((uq * s * t) * p, uq * (scaling * (t * p)), epsilon = 1.0e-7) && + + // (translation × scaling × rotation) × point = translation × (scaling × (rotation × point)) + relative_eq!((t * s * uq) * v, scaling * (uq * v), epsilon = 1.0e-7) && + relative_eq!((t * s * uq) * p, t * (scaling * (uq * p)), epsilon = 1.0e-7) && + + // (rotation × scaling × isometry) × point = rotation × (scaling × (isometry × point)) + relative_eq!((uq * s * i) * v, uq * (scaling * (i * v)), epsilon = 1.0e-7) && + relative_eq!((uq * s * i) * p, uq * (scaling * (i * p)), epsilon = 1.0e-7) && + + // (isometry × scaling × rotation) × point = isometry × (scaling × (rotation × point)) + relative_eq!((i * s * uq) * v, i * (scaling * (uq * v)), epsilon = 1.0e-7) && + relative_eq!((i * s * uq) * p, i * (scaling * (uq * p)), epsilon = 1.0e-7) && + + // (translation × scaling × isometry) × point = translation × (scaling × (isometry × point)) + relative_eq!((t * s * i) * v, (scaling * (i * v)), epsilon = 1.0e-7) && + relative_eq!((t * s * i) * p, t * (scaling * (i * p)), epsilon = 1.0e-7) && + + // (isometry × scaling × translation) × point = isometry × (scaling × (translation × point)) + relative_eq!((i * s * t) * v, i * (scaling * v), epsilon = 1.0e-7) && + relative_eq!((i * s * t) * p, i * (scaling * (t * p)), epsilon = 1.0e-7) && + + + /* + * Same as before but with scaling on the left. + */ + // (scaling × rotation × translation) × point = scaling × (rotation × (translation × point)) + relative_eq!((s * uq * t) * v, scaling * (uq * v), epsilon = 1.0e-7) && + relative_eq!((s * uq * t) * p, scaling * (uq * (t * p)), epsilon = 1.0e-7) && + + // (scaling × translation × rotation) × point = scaling × (translation × (rotation × point)) + relative_eq!((s * t * uq) * v, scaling * (uq * v), epsilon = 1.0e-7) && + relative_eq!((s * t * uq) * p, scaling * (t * (uq * p)), epsilon = 1.0e-7) && + + // (scaling × rotation × isometry) × point = scaling × (rotation × (isometry × point)) + relative_eq!((s * uq * i) * v, scaling * (uq * (i * v)), epsilon = 1.0e-7) && + relative_eq!((s * uq * i) * p, scaling * (uq * (i * p)), epsilon = 1.0e-7) && + + // (scaling × isometry × rotation) × point = scaling × (isometry × (rotation × point)) + relative_eq!((s * i * uq) * v, scaling * (i * (uq * v)), epsilon = 1.0e-7) && + relative_eq!((s * i * uq) * p, scaling * (i * (uq * p)), epsilon = 1.0e-7) && + + // (scaling × translation × isometry) × point = scaling × (translation × (isometry × point)) + relative_eq!((s * t * i) * v, (scaling * (i * v)), epsilon = 1.0e-7) && + relative_eq!((s * t * i) * p, scaling * (t * (i * p)), epsilon = 1.0e-7) && + + // (scaling × isometry × translation) × point = scaling × (isometry × (translation × point)) + relative_eq!((s * i * t) * v, scaling * (i * v), epsilon = 1.0e-7) && + relative_eq!((s * i * t) * p, scaling * (i * (t * p)), epsilon = 1.0e-7) + } + + fn all_op_exist(s: Similarity3, i: Isometry3, uq: UnitQuaternion, + t: Translation3, v: Vector3, p: Point3) -> bool { + let sMs = s * s; + let sMuq = s * uq; + let sDs = s / s; + let sDuq = s / uq; + + let sMp = s * p; + let sMv = s * v; + + let sMt = s * t; + let tMs = t * s; + + let uqMs = uq * s; + let uqDs = uq / s; + + let sMi = s * i; + let sDi = s / i; + + let iMs = i * s; + let iDs = i / s; + + let mut sMt1 = s; + let mut sMt2 = s; + + let mut sMs1 = s; + let mut sMs2 = s; + + let mut sMuq1 = s; + let mut sMuq2 = s; + + let mut sMi1 = s; + let mut sMi2 = s; + + let mut sDs1 = s; + let mut sDs2 = s; + + let mut sDuq1 = s; + let mut sDuq2 = s; + + let mut sDi1 = s; + let mut sDi2 = s; + + sMt1 *= t; + sMt2 *= &t; + + sMs1 *= s; + sMs2 *= &s; + + sMuq1 *= uq; + sMuq2 *= &uq; + + sMi1 *= i; + sMi2 *= &i; + + sDs1 /= s; + sDs2 /= &s; + + sDuq1 /= uq; + sDuq2 /= &uq; + + sDi1 /= i; + sDi2 /= &i; + + sMt == sMt1 && + sMt == sMt2 && + + sMs == sMs1 && + sMs == sMs2 && + + sMuq == sMuq1 && + sMuq == sMuq2 && + + sMi == sMi1 && + sMi == sMi2 && + + sDs == sDs1 && + sDs == sDs2 && + + sDuq == sDuq1 && + sDuq == sDuq2 && + + sDi == sDi1 && + sDi == sDi2 && + + sMs == &s * &s && + sMs == s * &s && + sMs == &s * s && + + sMuq == &s * &uq && + sMuq == s * &uq && + sMuq == &s * uq && + + sDs == &s / &s && + sDs == s / &s && + sDs == &s / s && + + sDuq == &s / &uq && + sDuq == s / &uq && + sDuq == &s / uq && + + sMp == &s * &p && + sMp == s * &p && + sMp == &s * p && + + sMv == &s * &v && + sMv == s * &v && + sMv == &s * v && + + sMt == &s * &t && + sMt == s * &t && + sMt == &s * t && + + tMs == &t * &s && + tMs == t * &s && + tMs == &t * s && + + uqMs == &uq * &s && + uqMs == uq * &s && + uqMs == &uq * s && + + uqDs == &uq / &s && + uqDs == uq / &s && + uqDs == &uq / s && + + sMi == &s * &i && + sMi == s * &i && + sMi == &s * i && + + sDi == &s / &i && + sDi == s / &i && + sDi == &s / i && + + iMs == &i * &s && + iMs == i * &s && + iMs == &i * s && + + iDs == &i / &s && + iDs == i / &s && + iDs == &i / s + } +); diff --git a/tests/transforms.rs b/tests/transforms.rs deleted file mode 100644 index 20c9391e..00000000 --- a/tests/transforms.rs +++ /dev/null @@ -1,280 +0,0 @@ -extern crate nalgebra as na; -extern crate rand; - -use rand::random; -use na::{Point2, Point3, Vector2, Vector3, Vector1, Rotation2, Rotation3, Perspective3, PerspectiveMatrix3, Orthographic3, OrthographicMatrix3, - Isometry2, Isometry3, Similarity2, Similarity3, BaseFloat, Transform}; - -#[test] -fn test_rotation2() { - for _ in 0usize .. 10000 { - let randmatrix: na::Rotation2 = na::one(); - let ang = Vector1::new(na::abs(&random::()) % ::pi()); - - assert!(na::approx_eq(&na::rotation(&na::append_rotation(&randmatrix, &ang)), &ang)); - } -} - -#[test] -fn test_inverse_rotation3() { - for _ in 0usize .. 10000 { - let randmatrix: Rotation3 = na::one(); - let dir: Vector3 = random(); - let ang = na::normalize(&dir) * (na::abs(&random::()) % ::pi()); - let rotation = na::append_rotation(&randmatrix, &ang); - - assert!(na::approx_eq(&(na::transpose(&rotation) * rotation), &na::one())); - } -} - -#[test] -fn test_rot3_rotation_between() { - let r1: Rotation3 = random(); - let r2: Rotation3 = random(); - - let delta = na::rotation_between(&r1, &r2); - - assert!(na::approx_eq(&(delta * r1), &r2)) -} - -#[test] -fn test_rot3_angle_between() { - let r1: Rotation3 = random(); - let r2: Rotation3 = random(); - - let delta = na::rotation_between(&r1, &r2); - let delta_angle = na::angle_between(&r1, &r2); - - assert!(na::approx_eq(&na::norm(&na::rotation(&delta)), &delta_angle)) -} - -#[test] -fn test_rot2_rotation_between() { - let r1: Rotation2 = random(); - let r2: Rotation2 = random(); - - let delta = na::rotation_between(&r1, &r2); - - assert!(na::approx_eq(&(delta * r1), &r2)) -} - -#[test] -fn test_rot2_angle_between() { - let r1: Rotation2 = random(); - let r2: Rotation2 = random(); - - let delta = na::rotation_between(&r1, &r2); - let delta_angle = na::angle_between(&r1, &r2); - - assert!(na::approx_eq(&na::norm(&na::rotation(&delta)), &delta_angle)) -} - - -#[test] -fn test_look_at_rh_iso3() { - for _ in 0usize .. 10000 { - let eye = random::>(); - let target = random::>(); - let up = random::>(); - let viewmatrix = Isometry3::look_at_rh(&eye, &target, &up); - - let origin: Point3 = na::origin(); - assert_eq!(&(viewmatrix * eye), &origin); - assert!(na::approx_eq(&na::normalize(&(viewmatrix * (target - eye))), &-Vector3::z())); - } -} - -#[test] -fn test_look_at_rh_rot3() { - for _ in 0usize .. 10000 { - let dir = random::>(); - let up = random::>(); - let viewmatrix = Rotation3::look_at_rh(&dir, &up); - - println!("found: {}", viewmatrix * dir); - assert!(na::approx_eq(&na::normalize(&(viewmatrix * dir)), &-Vector3::z())); - } -} - -#[test] -fn test_observer_frame_iso3() { - for _ in 0usize .. 10000 { - let eye = random::>(); - let target = random::>(); - let up = random::>(); - let observer = Isometry3::new_observer_frame(&eye, &target, &up); - - assert_eq!(&(observer * na::origin::>()), &eye); - assert!(na::approx_eq(&(observer * Vector3::z()), &na::normalize(&(target - eye)))); - } -} - -#[test] -fn test_observer_frame_rot3() { - for _ in 0usize .. 10000 { - let dir = random::>(); - let up = random::>(); - let observer = Rotation3::new_observer_frame(&dir, &up); - - assert!(na::approx_eq(&(observer * Vector3::z()), &na::normalize(&dir))); - } -} - -#[test] -fn test_persp() { - let mut p = Perspective3::new(42.0f64, 0.5, 1.5, 10.0); - let mut pm = PerspectiveMatrix3::new(42.0f64, 0.5, 1.5, 10.0); - assert!(p.to_matrix() == pm.to_matrix()); - assert!(p.aspect() == 42.0); - assert!(p.fovy() == 0.5); - assert!(p.znear() == 1.5); - assert!(p.zfar() == 10.0); - assert!(na::approx_eq(&pm.aspect(), &42.0)); - assert!(na::approx_eq(&pm.fovy(), &0.5)); - assert!(na::approx_eq(&pm.znear(), &1.5)); - assert!(na::approx_eq(&pm.zfar(), &10.0)); - - p.set_fovy(0.1); - pm.set_fovy(0.1); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_znear(24.0); - pm.set_znear(24.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_zfar(61.0); - pm.set_zfar(61.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_aspect(23.0); - pm.set_aspect(23.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - assert!(p.aspect() == 23.0); - assert!(p.fovy() == 0.1); - assert!(p.znear() == 24.0); - assert!(p.zfar() == 61.0); - assert!(na::approx_eq(&pm.aspect(), &23.0)); - assert!(na::approx_eq(&pm.fovy(), &0.1)); - assert!(na::approx_eq(&pm.znear(), &24.0)); - assert!(na::approx_eq(&pm.zfar(), &61.0)); -} - -#[test] -fn test_ortho() { - let mut p = Orthographic3::new(-0.3, 5.2, -3.9, -1.0, 1.5, 10.0); - let mut pm = OrthographicMatrix3::new(-0.3, 5.2, -3.9, -1.0, 1.5, 10.0); - assert!(p.to_matrix() == pm.to_matrix()); - assert!(p.left() == -0.3); - assert!(p.right() == 5.2); - assert!(p.bottom() == -3.9); - assert!(p.top() == -1.0); - assert!(p.znear() == 1.5); - assert!(p.zfar() == 10.0); - assert!(na::approx_eq(&pm.left(), &-0.3)); - assert!(na::approx_eq(&pm.right(), &5.2)); - assert!(na::approx_eq(&pm.bottom(), &-3.9)); - assert!(na::approx_eq(&pm.top(), &-1.0)); - assert!(na::approx_eq(&pm.znear(), &1.5)); - assert!(na::approx_eq(&pm.zfar(), &10.0)); - - p.set_left(0.1); - pm.set_left(0.1); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_right(10.1); - pm.set_right(10.1); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_top(24.0); - pm.set_top(24.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_bottom(-23.0); - pm.set_bottom(-23.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_zfar(61.0); - pm.set_zfar(61.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - p.set_znear(21.0); - pm.set_znear(21.0); - assert!(na::approx_eq(&p.to_matrix(), pm.as_matrix())); - - assert!(p.znear() == 21.0); - assert!(p.zfar() == 61.0); - assert!(na::approx_eq(&pm.znear(), &21.0)); - assert!(na::approx_eq(&pm.zfar(), &61.0)); -} - -macro_rules! test_transform_inverse_transform_impl( - ($fnname: ident, $t: ty, $p: ty) => ( - #[test] - fn $fnname() { - for _ in 0usize .. 10000 { - let randmatrix: $t = random(); - let expected: $p = random(); - - let computed = randmatrix.inverse_transform(&randmatrix.transform(&expected)); - println!("computed: {}, expected: {}", computed, expected); - - assert!(na::approx_eq(&computed, &expected)); - } - } - ); -); - -test_transform_inverse_transform_impl!(test_transform_inverse_transform_rot2, Rotation2, Point2); -test_transform_inverse_transform_impl!(test_transform_inverse_transform_rot3, Rotation3, Point3); -test_transform_inverse_transform_impl!(test_transform_inverse_transform_iso2, Isometry2, Point2); -test_transform_inverse_transform_impl!(test_transform_inverse_transform_iso3, Isometry3, Point3); -test_transform_inverse_transform_impl!(test_transform_inverse_transform_sim2, Similarity2, Point2); -test_transform_inverse_transform_impl!(test_transform_inverse_transform_sim3, Similarity3, Point3); - -macro_rules! test_transform_mul_assoc( - ($fnname: ident, $t1: ty, $t2: ty, $p: ty) => ( - #[test] - fn $fnname() { - for _ in 0usize .. 10000 { - let t1: $t1 = random(); - let t2: $t2 = random(); - let p: $p = random(); - - let t1p = t1 * p; - let t2p = t2 * p; - let t1t2 = t1 * t2; - let t2t1 = t2 * t1; - - assert!(na::approx_eq(&(t1t2 * p), &(t1 * t2p))); - assert!(na::approx_eq(&(t2t1 * p), &(t2 * t1p))); - } - } - ); -); - -test_transform_mul_assoc!(test_transform_inverse_transform_sim3_sim3_point3, Similarity3, Similarity3, Point3); -test_transform_mul_assoc!(test_transform_inverse_transform_sim3_iso3_point3, Similarity3, Isometry3, Point3); -test_transform_mul_assoc!(test_transform_inverse_transform_sim3_rot3_point3, Similarity3, Rotation3, Point3); -test_transform_mul_assoc!(test_transform_inverse_transform_iso3_iso3_point3, Isometry3, Isometry3, Point3); -test_transform_mul_assoc!(test_transform_inverse_transform_iso3_rot3_point3, Isometry3, Rotation3, Point3); -test_transform_mul_assoc!(test_transform_inverse_transform_rot3_rot3_point3, Rotation3, Rotation3, Point3); -test_transform_mul_assoc!(test_transform_inverse_transform_sim3_sim3_vec3, Similarity3, Similarity3, Vector3); -test_transform_mul_assoc!(test_transform_inverse_transform_sim3_iso3_vec3, Similarity3, Isometry3, Vector3); -test_transform_mul_assoc!(test_transform_inverse_transform_sim3_rot3_vec3, Similarity3, Rotation3, Vector3); -test_transform_mul_assoc!(test_transform_inverse_transform_iso3_iso3_vec3, Isometry3, Isometry3, Vector3); -test_transform_mul_assoc!(test_transform_inverse_transform_iso3_rot3_vec3, Isometry3, Rotation3, Vector3); -test_transform_mul_assoc!(test_transform_inverse_transform_rot3_rot3_vec3, Rotation3, Rotation3, Vector3); - -test_transform_mul_assoc!(test_transform_inverse_transform_sim2_sim2_point2, Similarity2, Similarity2, Point2); -test_transform_mul_assoc!(test_transform_inverse_transform_sim2_iso2_point2, Similarity2, Isometry2, Point2); -test_transform_mul_assoc!(test_transform_inverse_transform_sim2_rot2_point2, Similarity2, Rotation2, Point2); -test_transform_mul_assoc!(test_transform_inverse_transform_iso2_iso2_point2, Isometry2, Isometry2, Point2); -test_transform_mul_assoc!(test_transform_inverse_transform_iso2_rot2_point2, Isometry2, Rotation2, Point2); -test_transform_mul_assoc!(test_transform_inverse_transform_rot2_rot2_point2, Rotation2, Rotation2, Point2); -test_transform_mul_assoc!(test_transform_inverse_transform_sim2_sim2_vec2, Similarity2, Similarity2, Vector2); -test_transform_mul_assoc!(test_transform_inverse_transform_sim2_iso2_vec2, Similarity2, Isometry2, Vector2); -test_transform_mul_assoc!(test_transform_inverse_transform_sim2_rot2_vec2, Similarity2, Rotation2, Vector2); -test_transform_mul_assoc!(test_transform_inverse_transform_iso2_iso2_vec2, Isometry2, Isometry2, Vector2); -test_transform_mul_assoc!(test_transform_inverse_transform_iso2_rot2_vec2, Isometry2, Rotation2, Vector2); -test_transform_mul_assoc!(test_transform_inverse_transform_rot2_rot2_vec2, Rotation2, Rotation2, Vector2); diff --git a/tests/vec.rs b/tests/vec.rs deleted file mode 100644 index dfa69dc6..00000000 --- a/tests/vec.rs +++ /dev/null @@ -1,374 +0,0 @@ -extern crate rand; -#[cfg(feature="generic_sizes")] -extern crate typenum; -extern crate nalgebra as na; - -use rand::random; -use na::{Vector1, Vector2, Vector3, Vector4, Vector5, Vector6, Matrix3, Rotation2, Rotation3, Iterable, IterableMut}; - -#[cfg(feature="generic_sizes")] -use typenum::U10; -#[cfg(feature="generic_sizes")] -use na::VectorN; - - -macro_rules! test_iterator_impl( - ($t: ty, $n: ty) => ( - for _ in 0usize .. 10000 { - let v: $t = random(); - let mut mv: $t = v.clone(); - let n: $n = random(); - - let nv: $t = v.iter().map(|e| *e * n).collect(); - - for e in mv.iter_mut() { - *e = *e * n - } - - assert!(nv == mv && nv == v * n); - } - ) -); - -macro_rules! test_commut_dot_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - let v1 : $t = random(); - let v2 : $t = random(); - - assert!(na::approx_eq(&na::dot(&v1, &v2), &na::dot(&v2, &v1))); - } - ); -); - -macro_rules! test_scalar_op_impl( - ($t: ty, $n: ty) => ( - for _ in 0usize .. 10000 { - let v1 : $t = random(); - let n : $n = random(); - - assert!(na::approx_eq(&((v1 * n) / n), &v1)); - assert!(na::approx_eq(&((v1 / n) * n), &v1)); - assert!(na::approx_eq(&((v1 - n) + n), &v1)); - assert!(na::approx_eq(&((v1 + n) - n), &v1)); - - let mut v1 : $t = random(); - let v0 : $t = v1.clone(); - let n : $n = random(); - - v1 = v1 * n; - v1 = v1 / n; - - assert!(na::approx_eq(&v1, &v0)); - } - ); -); - -macro_rules! test_basis_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - na::canonical_basis(|e1: $t| { - na::canonical_basis(|e2: $t| { - assert!(e1 == e2 || na::approx_eq(&na::dot(&e1, &e2), &na::zero())); - - true - }); - - assert!(na::approx_eq(&na::norm(&e1), &na::one())); - - true - }) - } - ); -); - -macro_rules! test_subspace_basis_impl( - ($t: ty) => ( - for _ in 0usize .. 10000 { - let v : $t = random(); - let v1 = na::normalize(&v); - - na::orthonormal_subspace_basis(&v1, |e1| { - // check vectors are orthogonal to v1 - assert!(na::approx_eq(&na::dot(&v1, &e1), &na::zero())); - // check vectors form an orthonormal basis - assert!(na::approx_eq(&na::norm(&e1), &na::one())); - // check vectors form an ortogonal basis - na::orthonormal_subspace_basis(&v1, |e2| { - assert!(e1 == e2 || na::approx_eq(&na::dot(&e1, &e2), &na::zero())); - - true - }); - - true - }) - } - ); -); - -#[test] -fn test_cross_vec3() { - for _ in 0usize .. 10000 { - let v1 : Vector3 = random(); - let v2 : Vector3 = random(); - let v3 : Vector3 = na::cross(&v1, &v2); - - assert!(na::approx_eq(&na::dot(&v3, &v2), &na::zero())); - assert!(na::approx_eq(&na::dot(&v3, &v1), &na::zero())); - } -} - -#[test] -fn test_commut_dot_vec1() { - test_commut_dot_impl!(Vector1); -} - -#[test] -fn test_commut_dot_vec2() { - test_commut_dot_impl!(Vector2); -} - -#[test] -fn test_commut_dot_vec3() { - test_commut_dot_impl!(Vector3); -} - -#[test] -fn test_commut_dot_vec4() { - test_commut_dot_impl!(Vector4); -} - -#[test] -fn test_commut_dot_vec5() { - test_commut_dot_impl!(Vector5); -} - -#[test] -fn test_commut_dot_vec6() { - test_commut_dot_impl!(Vector6); -} - -#[test] -fn test_basis_vec1() { - test_basis_impl!(Vector1); -} - -#[test] -fn test_basis_vec2() { - test_basis_impl!(Vector2); -} - -#[test] -fn test_basis_vec3() { - test_basis_impl!(Vector3); -} - -#[test] -fn test_basis_vec4() { - test_basis_impl!(Vector4); -} - -#[test] -fn test_basis_vec5() { - test_basis_impl!(Vector5); -} - -#[test] -fn test_basis_vec6() { - test_basis_impl!(Vector6); -} - -#[test] -fn test_subspace_basis_vec1() { - test_subspace_basis_impl!(Vector1); -} - -#[test] -fn test_subspace_basis_vec2() { - test_subspace_basis_impl!(Vector2); -} - -#[test] -fn test_subspace_basis_vec3() { - test_subspace_basis_impl!(Vector3); -} - -#[test] -fn test_subspace_basis_vec4() { - test_subspace_basis_impl!(Vector4); -} - -#[test] -fn test_subspace_basis_vec5() { - test_subspace_basis_impl!(Vector5); -} - -#[test] -fn test_subspace_basis_vec6() { - test_subspace_basis_impl!(Vector6); -} - -#[test] -fn test_scalar_op_vec1() { - test_scalar_op_impl!(Vector1, f64); -} - -#[test] -fn test_scalar_op_vec2() { - test_scalar_op_impl!(Vector2, f64); -} - -#[test] -fn test_scalar_op_vec3() { - test_scalar_op_impl!(Vector3, f64); -} - -#[test] -fn test_scalar_op_vec4() { - test_scalar_op_impl!(Vector4, f64); -} - -#[test] -fn test_scalar_op_vec5() { - test_scalar_op_impl!(Vector5, f64); -} - -#[test] -fn test_scalar_op_vec6() { - test_scalar_op_impl!(Vector6, f64); -} - -#[test] -fn test_iterator_vec1() { - test_iterator_impl!(Vector1, f64); -} - -#[test] -fn test_iterator_vec2() { - test_iterator_impl!(Vector2, f64); -} - -#[test] -fn test_iterator_vec3() { - test_iterator_impl!(Vector3, f64); -} - -#[test] -fn test_iterator_vec4() { - test_iterator_impl!(Vector4, f64); -} - -#[test] -fn test_iterator_vec5() { - test_iterator_impl!(Vector5, f64); -} - -#[test] -fn test_iterator_vec6() { - test_iterator_impl!(Vector6, f64); -} - -#[test] -fn test_ord_vec3() { - // equality - assert!(Vector3::new(0.5f64, 0.5, 0.5) == Vector3::new(0.5, 0.5, 0.5)); - assert!(!(Vector3::new(1.5f64, 0.5, 0.5) == Vector3::new(0.5, 0.5, 0.5))); - assert!(Vector3::new(1.5f64, 0.5, 0.5) != Vector3::new(0.5, 0.5, 0.5)); - - // comparable - assert!(na::partial_cmp(&Vector3::new(0.5f64, 0.3, 0.3), &Vector3::new(1.0, 2.0, 1.0)).is_le()); - assert!(na::partial_cmp(&Vector3::new(0.5f64, 0.3, 0.3), &Vector3::new(1.0, 2.0, 1.0)).is_lt()); - assert!(na::partial_cmp(&Vector3::new(2.0f64, 4.0, 2.0), &Vector3::new(1.0, 2.0, 1.0)).is_ge()); - assert!(na::partial_cmp(&Vector3::new(2.0f64, 4.0, 2.0), &Vector3::new(1.0, 2.0, 1.0)).is_gt()); - - // not comparable - assert!(na::partial_cmp(&Vector3::new(0.0f64, 3.0, 0.0), &Vector3::new(1.0, 2.0, 1.0)).is_not_comparable()); -} - -#[test] -fn test_min_max_vec3() { - assert_eq!(na::sup(&Vector3::new(1.0f64, 2.0, 3.0), &Vector3::new(3.0, 2.0, 1.0)), Vector3::new(3.0, 2.0, 3.0)); - assert_eq!(na::inf(&Vector3::new(1.0f64, 2.0, 3.0), &Vector3::new(3.0, 2.0, 1.0)), Vector3::new(1.0, 2.0, 1.0)); -} - -#[test] -fn test_outer_vec3() { - assert_eq!( - na::outer(&Vector3::new(1.0f64, 2.0, 3.0), &Vector3::new(4.0, 5.0, 6.0)), - Matrix3::new( - 4.0, 5.0, 6.0, - 8.0, 10.0, 12.0, - 12.0, 15.0, 18.0)); -} - -#[cfg(feature="generic_sizes")] -#[test] -fn test_vecn10_add_mul() { - for _ in 0usize .. 10000 { - let v1: VectorN = random(); - - assert!(na::approx_eq(&(v1 + v1), &(v1 * 2.0))) - } -} - - -#[test] -fn test_vec3_rotation_between() { - for _ in 0usize .. 10000 { - let v1: Vector3 = random(); - - let mut v2: Vector3 = random(); - v2 = na::normalize(&v2) * na::norm(&v1); - - let rotation = na::rotation_between(&v1, &v2); - - assert!(na::approx_eq(&(rotation * v1), &v2)) - } -} - -#[test] -fn test_vec3_angle_between() { - for _ in 0usize .. 10000 { - let vector: Vector3 = random(); - let other: Vector3 = random(); - - // Ensure the axis we are using is orthogonal to `vector`. - let axis_ang = na::cross(&vector, &other); - let ang = na::norm(&axis_ang); - let rotation = Rotation3::new(axis_ang); - - let delta = na::angle_between(&vector, &(rotation * vector)); - - assert!(na::approx_eq(&ang, &delta)) - } -} - - -#[test] -fn test_vec2_rotation_between() { - for _ in 0usize .. 10000 { - let v1: Vector2 = random(); - - let mut v2: Vector2 = random(); - v2 = na::normalize(&v2) * na::norm(&v1); - - let rotation = na::rotation_between(&v1, &v2); - - assert!(na::approx_eq(&(rotation * v1), &v2)) - } -} - -#[test] -fn test_vec2_angle_between() { - for _ in 0usize .. 10000 { - let axis_ang: Vector1 = random(); - let ang = na::norm(&axis_ang); - - let rotation: Rotation2 = Rotation2::new(axis_ang); - let vector: Vector2 = random(); - - let delta = na::angle_between(&vector, &(rotation * vector)); - - assert!(na::approx_eq(&ang, &delta)) - } -} From 377f8b55966813dd025e09838fc70a5495266179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 4 Dec 2016 23:00:12 +0100 Subject: [PATCH 2/8] Fix travis.yml. --- .travis.yml | 2 +- Makefile | 31 ++++++------------------------- tests/quaternion.rs | 2 +- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 88f0886a..2b1fd312 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,5 @@ language: rust script: - rustc --version - cargo --version - - cargo build --verbose --features "arbitrary generic_sizes" + - cargo build --verbose --features arbitrary - cargo test --verbose --features arbitrary diff --git a/Makefile b/Makefile index 78a080ee..f9586c02 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,11 @@ -tmp=_git_distcheck - all: - cargo build --release --features "arbitrary generic_sizes abstract_algebra" - -test: - cargo test --features "arbitrary generic_sizes abstract_algebra" - - -bench: - cargo bench --features "arbitrary generic_sizes abstract_algebra" - + cargo build --features "arbitrary" doc: - cargo doc --no-deps --features "arbitrary generic_sizes abstract_algebra" + cargo doc +bench: + cargo bench -clean: - cargo clean - -distcheck: - rm -rf $(tmp) - git clone --recursive . $(tmp) - make -C $(tmp) - make -C $(tmp) test - make -C $(tmp) bench - rm -rf $(tmp) - -.PHONY:doc -.PHONY:test +test: + cargo test --features "arbitrary" diff --git a/tests/quaternion.rs b/tests/quaternion.rs index bcd5de64..bd7d1962 100644 --- a/tests/quaternion.rs +++ b/tests/quaternion.rs @@ -100,7 +100,7 @@ quickcheck!( fn all_op_exist(q: Quaternion, uq: UnitQuaternion, v: Vector3, p: Point3, r: Rotation3, s: f64) -> bool { - let uv = Unit::new(v); + let uv = Unit::new_normalize(v); let qpq = q + q; let qmq = q - q; From 086e6e719f53fecba6dadad2e953a487976387f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 12 Feb 2017 18:17:09 +0100 Subject: [PATCH 3/8] Doc + slerp + conversions. --- Cargo.toml | 6 +- Makefile | 6 +- README.md | 74 +--------- examples/dimensional_genericity.rs | 66 +++++++++ examples/homogeneous_coordinates.rs | 45 ++++++ examples/identity.rs | 42 ++++++ examples/matrix_construction.rs | 62 +++++++++ examples/mvp.rs | 28 ++++ examples/raw_pointer.rs | 35 +++++ examples/scalar_genericity.rs | 34 +++++ examples/screen_to_view_coords.rs | 24 ++++ examples/transform_conversion.rs | 23 ++++ examples/transform_matrix4.rs | 39 ++++++ examples/transform_vector_point.rs | 20 +++ examples/transform_vector_point3.rs | 27 ++++ examples/transformation_pointer.rs | 25 ++++ examples/unit_wrapper.rs | 30 ++++ src/core/alias.rs | 48 +++++++ src/core/allocator.rs | 3 + src/core/cg.rs | 20 +-- src/core/constraint.rs | 28 +++- src/core/construction.rs | 16 ++- src/core/coordinates.rs | 10 +- src/core/decompositions.rs | 16 +-- src/core/default_allocator.rs | 5 + src/core/dimension.rs | 25 +++- src/core/iter.rs | 2 + src/core/matrix.rs | 85 +++++++++++- src/core/matrix_alga.rs | 2 +- src/core/matrix_array.rs | 104 +++++++++++++- src/core/matrix_slice.rs | 22 ++- src/core/matrix_vec.rs | 5 +- src/core/mod.rs | 4 +- src/core/ops.rs | 40 +++++- src/core/storage.rs | 8 ++ src/core/unit.rs | 70 ++++++---- src/geometry/isometry.rs | 16 ++- src/geometry/isometry_ops.rs | 16 +-- src/geometry/mod.rs | 3 + src/geometry/orthographic.rs | 47 +++++-- src/geometry/perspective.rs | 35 ++++- src/geometry/point.rs | 11 +- src/geometry/quaternion.rs | 134 +++++++++++++++++- src/geometry/quaternion_construction.rs | 43 ++++-- src/geometry/quaternion_coordinates.rs | 2 +- src/geometry/rotation.rs | 4 +- src/geometry/rotation_alga.rs | 15 +- src/geometry/rotation_ops.rs | 2 +- src/geometry/rotation_specialization.rs | 8 +- src/geometry/similarity.rs | 20 ++- src/geometry/similarity_alias.rs | 2 +- src/geometry/similarity_construction.rs | 14 +- src/geometry/transform.rs | 33 +++-- src/geometry/transform_alias.rs | 25 +++- src/geometry/transform_ops.rs | 117 ++++++++-------- src/geometry/translation.rs | 4 +- src/geometry/unit_complex.rs | 62 ++++++--- src/geometry/unit_complex_conversion.rs | 173 ++++++++++++++++++++++++ src/geometry/unit_complex_ops.rs | 98 +++++++++++++- src/lib.rs | 110 +++++++++++++-- tests/isometry.rs | 40 +++++- tests/matrix_slice.rs | 5 - tests/projection.rs | 51 +++++++ tests/rotation.rs | 2 +- tests/serde.rs | 47 +++++++ tests/unit_complex.rs | 171 +++++++++++++++++++++++ 66 files changed, 2074 insertions(+), 335 deletions(-) create mode 100644 examples/dimensional_genericity.rs create mode 100644 examples/homogeneous_coordinates.rs create mode 100644 examples/identity.rs create mode 100644 examples/matrix_construction.rs create mode 100644 examples/mvp.rs create mode 100644 examples/raw_pointer.rs create mode 100644 examples/scalar_genericity.rs create mode 100644 examples/screen_to_view_coords.rs create mode 100644 examples/transform_conversion.rs create mode 100644 examples/transform_matrix4.rs create mode 100644 examples/transform_vector_point.rs create mode 100644 examples/transform_vector_point3.rs create mode 100644 examples/transformation_pointer.rs create mode 100644 examples/unit_wrapper.rs create mode 100644 src/geometry/unit_complex_conversion.rs create mode 100644 tests/projection.rs create mode 100644 tests/serde.rs create mode 100644 tests/unit_complex.rs diff --git a/Cargo.toml b/Cargo.toml index 37a43717..398f0db9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ path = "src/lib.rs" arbitrary = [ "quickcheck" ] [dependencies] -rustc-serialize = "0.3" typenum = "1.4" generic-array = "0.2" rand = "0.3" @@ -27,8 +26,13 @@ num-traits = "0.1" num-complex = "0.1" approx = "0.1" alga = "0.4" +serde = "0.9" +serde_derive = "0.9" # clippy = "*" [dependencies.quickcheck] optional = true version = "0.3" + +[dev-dependencies] +serde_json = "0.9" diff --git a/Makefile b/Makefile index f9586c02..00457c11 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ all: - cargo build --features "arbitrary" + CARGO_INCREMENTAL=1 cargo build --features "arbitrary" doc: - cargo doc + CARGO_INCREMENTAL=1 cargo doc bench: cargo bench test: - cargo test --features "arbitrary" + CARGO_INCREMENTAL=1 cargo test --features "arbitrary" diff --git a/README.md b/README.md index c860659c..7d6a99fa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +