Merge pull request #74 from aepsil0n/arbitrary
Implement Arbitrary for (almost) all types.
This commit is contained in:
commit
c896a34a8b
|
@ -1 +1,8 @@
|
||||||
language: rust
|
language: rust
|
||||||
|
|
||||||
|
script:
|
||||||
|
- rustc --version
|
||||||
|
- cargo --version
|
||||||
|
- cargo build --verbose
|
||||||
|
- cargo test --verbose --features arbitrary
|
||||||
|
- cargo bench --verbose
|
||||||
|
|
|
@ -15,5 +15,12 @@ license = "BSD-3-Clause"
|
||||||
name = "nalgebra"
|
name = "nalgebra"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Generate arbitrary instances of nalgebra types for testing with quickcheck
|
||||||
|
arbitrary = ["quickcheck"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustc-serialize = "*"
|
rustc-serialize = "*"
|
||||||
|
|
||||||
|
[dependencies.quickcheck]
|
||||||
|
optional = true
|
||||||
|
|
|
@ -89,6 +89,9 @@ Feel free to add your project to this list if you happen to use **nalgebra**!
|
||||||
|
|
||||||
extern crate "rustc-serialize" as rustc_serialize;
|
extern crate "rustc-serialize" as rustc_serialize;
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
extern crate quickcheck;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ use structs::dvec::DVec;
|
||||||
use traits::operations::{Inv, Transpose, Mean, Cov};
|
use traits::operations::{Inv, Transpose, Mean, Cov};
|
||||||
use traits::structure::{Cast, ColSlice, RowSlice, Diag, Eye, Indexable, Shape, Zero, One, BaseNum};
|
use traits::structure::{Cast, ColSlice, RowSlice, Diag, Eye, Indexable, Shape, Zero, One, BaseNum};
|
||||||
use std::fmt::{Show, Formatter, Result, String};
|
use std::fmt::{Show, Formatter, Result, String};
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// Matrix with dimensions unknown at compile-time.
|
/// Matrix with dimensions unknown at compile-time.
|
||||||
|
@ -703,3 +705,13 @@ impl<N: Copy + Sub<N, Output = N>> Sub<N> for DMat<N> {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary> Arbitrary for DMat<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> DMat<N> {
|
||||||
|
DMat::from_fn(
|
||||||
|
Arbitrary::arbitrary(g), Arbitrary::arbitrary(g),
|
||||||
|
|_, _| Arbitrary::arbitrary(g)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut};
|
||||||
use traits::operations::{ApproxEq, Axpy};
|
use traits::operations::{ApproxEq, Axpy};
|
||||||
use traits::geometry::{Dot, Norm};
|
use traits::geometry::{Dot, Norm};
|
||||||
use traits::structure::{Iterable, IterableMut, Indexable, Shape, BaseFloat, BaseNum, Zero, One};
|
use traits::structure::{Iterable, IterableMut, Indexable, Shape, BaseFloat, BaseNum, Zero, One};
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
/// Heap allocated, dynamically sized vector.
|
/// Heap allocated, dynamically sized vector.
|
||||||
#[derive(Eq, PartialEq, Show, Clone)]
|
#[derive(Eq, PartialEq, Show, Clone)]
|
||||||
|
@ -78,6 +80,13 @@ impl<N> FromIterator<N> for DVec<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary> Arbitrary for DVec<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> DVec<N> {
|
||||||
|
DVec { at: Arbitrary::arbitrary(g) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dvec_impl!(DVec);
|
dvec_impl!(DVec);
|
||||||
|
|
||||||
|
|
|
@ -520,5 +520,13 @@ macro_rules! small_dvec_from_impl (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + Zero> Arbitrary for $dvec<N> {
|
||||||
|
#[inline]
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> $dvec<N> {
|
||||||
|
$dvec::from_fn(g.gen_range(0, $dim), |_| Arbitrary::arbitrary(g))
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,6 +15,9 @@ use structs::vec::{Vec1, Vec2, Vec3, Vec4};
|
||||||
use structs::pnt::{Pnt2, Pnt3, Pnt4};
|
use structs::pnt::{Pnt2, Pnt3, Pnt4};
|
||||||
use structs::rot::{Rot2, Rot3, Rot4};
|
use structs::rot::{Rot2, Rot3, Rot4};
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// Two dimensional isometry.
|
/// Two dimensional isometry.
|
||||||
///
|
///
|
||||||
|
@ -112,6 +115,7 @@ translate_impl!(Iso2, Pnt2);
|
||||||
iso_mul_iso_impl!(Iso2);
|
iso_mul_iso_impl!(Iso2);
|
||||||
iso_mul_pnt_impl!(Iso2, Pnt2);
|
iso_mul_pnt_impl!(Iso2, Pnt2);
|
||||||
pnt_mul_iso_impl!(Iso2, Pnt2);
|
pnt_mul_iso_impl!(Iso2, Pnt2);
|
||||||
|
arbitrary_iso_impl!(Iso2);
|
||||||
|
|
||||||
iso_impl!(Iso3, Rot3, Vec3, Vec3);
|
iso_impl!(Iso3, Rot3, Vec3, Vec3);
|
||||||
rotation_matrix_impl!(Iso3, Rot3, Vec3, Vec3);
|
rotation_matrix_impl!(Iso3, Rot3, Vec3, Vec3);
|
||||||
|
@ -131,6 +135,7 @@ translate_impl!(Iso3, Pnt3);
|
||||||
iso_mul_iso_impl!(Iso3);
|
iso_mul_iso_impl!(Iso3);
|
||||||
iso_mul_pnt_impl!(Iso3, Pnt3);
|
iso_mul_pnt_impl!(Iso3, Pnt3);
|
||||||
pnt_mul_iso_impl!(Iso3, Pnt3);
|
pnt_mul_iso_impl!(Iso3, Pnt3);
|
||||||
|
arbitrary_iso_impl!(Iso3);
|
||||||
|
|
||||||
// iso_impl!(Iso4, Rot4, Vec4, Vec4);
|
// iso_impl!(Iso4, Rot4, Vec4, Vec4);
|
||||||
// rotation_matrix_impl!(Iso4, Rot4, Vec4, Vec4);
|
// rotation_matrix_impl!(Iso4, Rot4, Vec4, Vec4);
|
||||||
|
@ -150,3 +155,5 @@ translate_impl!(Iso4, Pnt4);
|
||||||
iso_mul_iso_impl!(Iso4);
|
iso_mul_iso_impl!(Iso4);
|
||||||
iso_mul_pnt_impl!(Iso4, Pnt4);
|
iso_mul_pnt_impl!(Iso4, Pnt4);
|
||||||
pnt_mul_iso_impl!(Iso4, Pnt4);
|
pnt_mul_iso_impl!(Iso4, Pnt4);
|
||||||
|
// FIXME: as soon as Rot4<N>: Arbitrary
|
||||||
|
// arbitrary_iso_impl!(Iso4);
|
||||||
|
|
|
@ -364,3 +364,17 @@ macro_rules! absolute_rotate_impl(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
macro_rules! arbitrary_iso_impl(
|
||||||
|
($t: ident) => (
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + BaseFloat> Arbitrary for $t<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> $t<N> {
|
||||||
|
$t::new_with_rotmat(
|
||||||
|
Arbitrary::arbitrary(g),
|
||||||
|
Arbitrary::arbitrary(g)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
@ -15,6 +15,8 @@ use traits::structure::{Cast, Row, Col, Iterable, IterableMut, Dim, Indexable,
|
||||||
use traits::operations::{Absolute, Transpose, Inv, Outer, EigenQR};
|
use traits::operations::{Absolute, Transpose, Inv, Outer, EigenQR};
|
||||||
use traits::geometry::{ToHomogeneous, FromHomogeneous, Orig};
|
use traits::geometry::{ToHomogeneous, FromHomogeneous, Orig};
|
||||||
use linalg;
|
use linalg;
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// Special identity matrix. All its operation are no-ops.
|
/// Special identity matrix. All its operation are no-ops.
|
||||||
|
@ -72,6 +74,7 @@ to_homogeneous_impl!(Mat1, Mat2, 1, 2);
|
||||||
from_homogeneous_impl!(Mat1, Mat2, 1, 2);
|
from_homogeneous_impl!(Mat1, Mat2, 1, 2);
|
||||||
outer_impl!(Vec1, Mat1);
|
outer_impl!(Vec1, Mat1);
|
||||||
eigen_qr_impl!(Mat1, Vec1);
|
eigen_qr_impl!(Mat1, Vec1);
|
||||||
|
arbitrary_impl!(Mat1, m11);
|
||||||
|
|
||||||
/// Square matrix of dimension 2.
|
/// Square matrix of dimension 2.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -120,6 +123,7 @@ to_homogeneous_impl!(Mat2, Mat3, 2, 3);
|
||||||
from_homogeneous_impl!(Mat2, Mat3, 2, 3);
|
from_homogeneous_impl!(Mat2, Mat3, 2, 3);
|
||||||
outer_impl!(Vec2, Mat2);
|
outer_impl!(Vec2, Mat2);
|
||||||
eigen_qr_impl!(Mat2, Vec2);
|
eigen_qr_impl!(Mat2, Vec2);
|
||||||
|
arbitrary_impl!(Mat2, m11, m12, m21, m22);
|
||||||
|
|
||||||
/// Square matrix of dimension 3.
|
/// Square matrix of dimension 3.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -202,6 +206,11 @@ to_homogeneous_impl!(Mat3, Mat4, 3, 4);
|
||||||
from_homogeneous_impl!(Mat3, Mat4, 3, 4);
|
from_homogeneous_impl!(Mat3, Mat4, 3, 4);
|
||||||
outer_impl!(Vec3, Mat3);
|
outer_impl!(Vec3, Mat3);
|
||||||
eigen_qr_impl!(Mat3, Vec3);
|
eigen_qr_impl!(Mat3, Vec3);
|
||||||
|
arbitrary_impl!(Mat3,
|
||||||
|
m11, m12, m13,
|
||||||
|
m21, m22, m23,
|
||||||
|
m31, m32, m33
|
||||||
|
);
|
||||||
|
|
||||||
/// Square matrix of dimension 4.
|
/// Square matrix of dimension 4.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -302,6 +311,12 @@ to_homogeneous_impl!(Mat4, Mat5, 4, 5);
|
||||||
from_homogeneous_impl!(Mat4, Mat5, 4, 5);
|
from_homogeneous_impl!(Mat4, Mat5, 4, 5);
|
||||||
outer_impl!(Vec4, Mat4);
|
outer_impl!(Vec4, Mat4);
|
||||||
eigen_qr_impl!(Mat4, Vec4);
|
eigen_qr_impl!(Mat4, Vec4);
|
||||||
|
arbitrary_impl!(Mat4,
|
||||||
|
m11, m12, m13, m14,
|
||||||
|
m21, m22, m23, m24,
|
||||||
|
m31, m32, m33, m34,
|
||||||
|
m41, m42, m43, m44
|
||||||
|
);
|
||||||
|
|
||||||
/// Square matrix of dimension 5.
|
/// Square matrix of dimension 5.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -416,6 +431,13 @@ to_homogeneous_impl!(Mat5, Mat6, 5, 6);
|
||||||
from_homogeneous_impl!(Mat5, Mat6, 5, 6);
|
from_homogeneous_impl!(Mat5, Mat6, 5, 6);
|
||||||
outer_impl!(Vec5, Mat5);
|
outer_impl!(Vec5, Mat5);
|
||||||
eigen_qr_impl!(Mat5, Vec5);
|
eigen_qr_impl!(Mat5, Vec5);
|
||||||
|
arbitrary_impl!(Mat5,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
/// Square matrix of dimension 6.
|
/// Square matrix of dimension 6.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -532,3 +554,11 @@ row_slice_impl!(Mat6, Vec6, DVec6, 6);
|
||||||
diag_impl!(Mat6, Vec6, 6);
|
diag_impl!(Mat6, Vec6, 6);
|
||||||
outer_impl!(Vec6, Mat6);
|
outer_impl!(Vec6, Mat6);
|
||||||
eigen_qr_impl!(Mat6, Vec6);
|
eigen_qr_impl!(Mat6, Vec6);
|
||||||
|
arbitrary_impl!(Mat6,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
|
@ -2,6 +2,10 @@ use std::num;
|
||||||
use traits::structure::BaseFloat;
|
use traits::structure::BaseFloat;
|
||||||
use structs::{Pnt3, Vec3, Mat4};
|
use structs::{Pnt3, Vec3, Mat4};
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// A 3D orthographic projection stored without any matrix.
|
/// A 3D orthographic projection stored without any matrix.
|
||||||
///
|
///
|
||||||
/// Reading or modifying its individual properties is cheap but applying the transformation is costly.
|
/// Reading or modifying its individual properties is cheap but applying the transformation is costly.
|
||||||
|
@ -47,6 +51,17 @@ impl<N: BaseFloat> Ortho3<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + BaseFloat> Arbitrary for Ortho3<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> Ortho3<N> {
|
||||||
|
let width = reject(g, |x| !::is_zero(x));
|
||||||
|
let height = reject(g, |x| !::is_zero(x));
|
||||||
|
let znear = Arbitrary::arbitrary(g);
|
||||||
|
let zfar = reject(g, |&x: &N| !::is_zero(&(x - znear)));
|
||||||
|
Ortho3::new(width, height, znear, zfar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<N: BaseFloat + Clone> Ortho3<N> {
|
impl<N: BaseFloat + Clone> Ortho3<N> {
|
||||||
/// The width of the view cuboid.
|
/// The width of the view cuboid.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -232,3 +247,20 @@ impl<N: BaseFloat + Clone> OrthoMat3<N> {
|
||||||
self.mat.clone()
|
self.mat.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + BaseFloat> Arbitrary for OrthoMat3<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> OrthoMat3<N> {
|
||||||
|
let x: Ortho3<N> = Arbitrary::arbitrary(g);
|
||||||
|
x.to_persp_mat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Simple helper function for rejection sampling
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
#[inline]
|
||||||
|
pub fn reject<G: Gen, F: FnMut(&T) -> bool, T: Arbitrary>(g: &mut G, f: F) -> T {
|
||||||
|
use std::iter::repeat;
|
||||||
|
repeat(()).map(|_| Arbitrary::arbitrary(g)).filter(f).next().unwrap()
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use traits::structure::BaseFloat;
|
use traits::structure::BaseFloat;
|
||||||
use structs::{Pnt3, Vec3, Mat4};
|
use structs::{Pnt3, Vec3, Mat4};
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// A 3D perspective projection stored without any matrix.
|
/// A 3D perspective projection stored without any matrix.
|
||||||
///
|
///
|
||||||
/// Reading or modifying its individual properties is cheap but applying the transformation is costly.
|
/// Reading or modifying its individual properties is cheap but applying the transformation is costly.
|
||||||
|
@ -45,6 +49,16 @@ impl<N: BaseFloat> Persp3<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + BaseFloat> Arbitrary for Persp3<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> Persp3<N> {
|
||||||
|
use structs::ortho::reject;
|
||||||
|
let znear = Arbitrary::arbitrary(g);
|
||||||
|
let zfar = reject(g, |&x: &N| !::is_zero(&(x - znear)));
|
||||||
|
Persp3::new(Arbitrary::arbitrary(g), Arbitrary::arbitrary(g), znear, zfar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<N: BaseFloat + Clone> Persp3<N> {
|
impl<N: BaseFloat + Clone> Persp3<N> {
|
||||||
/// Gets the `width / height` aspect ratio.
|
/// Gets the `width / height` aspect ratio.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -263,3 +277,11 @@ impl<N: BaseFloat + Clone> PerspMat3<N> {
|
||||||
self.mat.clone()
|
self.mat.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + BaseFloat> Arbitrary for PerspMat3<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> PerspMat3<N> {
|
||||||
|
let x: Persp3<N> = Arbitrary::arbitrary(g);
|
||||||
|
x.to_persp_mat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ use traits::structure::{Cast, Dim, Indexable, Iterable, IterableMut, PntAsVec, S
|
||||||
NumPnt, FloatPnt, BaseFloat, BaseNum, Zero, One, Bounded};
|
NumPnt, FloatPnt, BaseFloat, BaseNum, Zero, One, Bounded};
|
||||||
use traits::geometry::{Orig, FromHomogeneous, ToHomogeneous};
|
use traits::geometry::{Orig, FromHomogeneous, ToHomogeneous};
|
||||||
use structs::vec::{Vec1, Vec2, Vec3, Vec4, Vec5, Vec6};
|
use structs::vec::{Vec1, Vec2, Vec3, Vec4, Vec5, Vec6};
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// Point of dimension 0.
|
/// Point of dimension 0.
|
||||||
|
@ -69,6 +71,7 @@ iterable_mut_impl!(Pnt1, 1);
|
||||||
pnt_to_homogeneous_impl!(Pnt1, Pnt2, y, x);
|
pnt_to_homogeneous_impl!(Pnt1, Pnt2, y, x);
|
||||||
pnt_from_homogeneous_impl!(Pnt1, Pnt2, y, x);
|
pnt_from_homogeneous_impl!(Pnt1, Pnt2, y, x);
|
||||||
num_float_pnt_impl!(Pnt1, Vec1);
|
num_float_pnt_impl!(Pnt1, Vec1);
|
||||||
|
arbitrary_pnt_impl!(Pnt1, x);
|
||||||
|
|
||||||
/// Point of dimension 2.
|
/// Point of dimension 2.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -109,6 +112,7 @@ iterable_mut_impl!(Pnt2, 2);
|
||||||
pnt_to_homogeneous_impl!(Pnt2, Pnt3, z, x, y);
|
pnt_to_homogeneous_impl!(Pnt2, Pnt3, z, x, y);
|
||||||
pnt_from_homogeneous_impl!(Pnt2, Pnt3, z, x, y);
|
pnt_from_homogeneous_impl!(Pnt2, Pnt3, z, x, y);
|
||||||
num_float_pnt_impl!(Pnt2, Vec2);
|
num_float_pnt_impl!(Pnt2, Vec2);
|
||||||
|
arbitrary_pnt_impl!(Pnt2, x, y);
|
||||||
|
|
||||||
/// Point of dimension 3.
|
/// Point of dimension 3.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -151,6 +155,7 @@ iterable_mut_impl!(Pnt3, 3);
|
||||||
pnt_to_homogeneous_impl!(Pnt3, Pnt4, w, x, y, z);
|
pnt_to_homogeneous_impl!(Pnt3, Pnt4, w, x, y, z);
|
||||||
pnt_from_homogeneous_impl!(Pnt3, Pnt4, w, x, y, z);
|
pnt_from_homogeneous_impl!(Pnt3, Pnt4, w, x, y, z);
|
||||||
num_float_pnt_impl!(Pnt3, Vec3);
|
num_float_pnt_impl!(Pnt3, Vec3);
|
||||||
|
arbitrary_pnt_impl!(Pnt3, x, y, z);
|
||||||
|
|
||||||
/// Point of dimension 4.
|
/// Point of dimension 4.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -195,6 +200,7 @@ iterable_mut_impl!(Pnt4, 4);
|
||||||
pnt_to_homogeneous_impl!(Pnt4, Pnt5, a, x, y, z, w);
|
pnt_to_homogeneous_impl!(Pnt4, Pnt5, a, x, y, z, w);
|
||||||
pnt_from_homogeneous_impl!(Pnt4, Pnt5, a, x, y, z, w);
|
pnt_from_homogeneous_impl!(Pnt4, Pnt5, a, x, y, z, w);
|
||||||
num_float_pnt_impl!(Pnt4, Vec4);
|
num_float_pnt_impl!(Pnt4, Vec4);
|
||||||
|
arbitrary_pnt_impl!(Pnt4, x, y, z, w);
|
||||||
|
|
||||||
/// Point of dimension 5.
|
/// Point of dimension 5.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -241,6 +247,7 @@ iterable_mut_impl!(Pnt5, 5);
|
||||||
pnt_to_homogeneous_impl!(Pnt5, Pnt6, b, x, y, z, w, a);
|
pnt_to_homogeneous_impl!(Pnt5, Pnt6, b, x, y, z, w, a);
|
||||||
pnt_from_homogeneous_impl!(Pnt5, Pnt6, b, x, y, z, w, a);
|
pnt_from_homogeneous_impl!(Pnt5, Pnt6, b, x, y, z, w, a);
|
||||||
num_float_pnt_impl!(Pnt5, Vec5);
|
num_float_pnt_impl!(Pnt5, Vec5);
|
||||||
|
arbitrary_pnt_impl!(Pnt5, x, y, z, w, a);
|
||||||
|
|
||||||
/// Point of dimension 6.
|
/// Point of dimension 6.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -287,3 +294,4 @@ axpy_impl!(Pnt6, x, y, z, w, a, b);
|
||||||
iterable_impl!(Pnt6, 6);
|
iterable_impl!(Pnt6, 6);
|
||||||
iterable_mut_impl!(Pnt6, 6);
|
iterable_mut_impl!(Pnt6, 6);
|
||||||
num_float_pnt_impl!(Pnt6, Vec6);
|
num_float_pnt_impl!(Pnt6, Vec6);
|
||||||
|
arbitrary_pnt_impl!(Pnt6, x, y, z, w, a, b);
|
||||||
|
|
|
@ -141,3 +141,17 @@ macro_rules! num_float_pnt_impl(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
macro_rules! arbitrary_pnt_impl(
|
||||||
|
($t: ident, $($compN: ident),*) => (
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary> Arbitrary for $t<N> {
|
||||||
|
#[inline]
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> $t<N> {
|
||||||
|
$t {
|
||||||
|
$($compN: Arbitrary::arbitrary(g),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
@ -15,6 +15,10 @@ use traits::structure::{Cast, Indexable, Iterable, IterableMut, Dim, Shape, Base
|
||||||
One, Bounded};
|
One, Bounded};
|
||||||
use traits::geometry::{Norm, Rotation, Rotate, Transform};
|
use traits::geometry::{Norm, Rotation, Rotate, Transform};
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// A quaternion.
|
/// A quaternion.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
pub struct Quat<N> {
|
pub struct Quat<N> {
|
||||||
|
@ -146,6 +150,7 @@ impl<N: ApproxEq<N> + BaseFloat> Div<Quat<N>> for Quat<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A unit quaternion that can represent a 3D rotation.
|
/// A unit quaternion that can represent a 3D rotation.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Show, Copy)]
|
||||||
pub struct UnitQuat<N> {
|
pub struct UnitQuat<N> {
|
||||||
|
@ -470,6 +475,14 @@ impl<N: BaseNum> Transform<Pnt3<N>> for UnitQuat<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + BaseFloat> Arbitrary for UnitQuat<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> UnitQuat<N> {
|
||||||
|
UnitQuat::new(Arbitrary::arbitrary(g))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ord_impl!(Quat, w, i, j, k);
|
ord_impl!(Quat, w, i, j, k);
|
||||||
vec_axis_impl!(Quat, w, i, j, k);
|
vec_axis_impl!(Quat, w, i, j, k);
|
||||||
vec_cast_impl!(Quat, w, i, j, k);
|
vec_cast_impl!(Quat, w, i, j, k);
|
||||||
|
@ -495,5 +508,6 @@ bounded_impl!(Quat, w, i, j, k);
|
||||||
axpy_impl!(Quat, w, i, j, k);
|
axpy_impl!(Quat, w, i, j, k);
|
||||||
iterable_impl!(Quat, 4);
|
iterable_impl!(Quat, 4);
|
||||||
iterable_mut_impl!(Quat, 4);
|
iterable_mut_impl!(Quat, 4);
|
||||||
|
arbitrary_impl!(Quat, w, i, j, k);
|
||||||
|
|
||||||
dim_impl!(UnitQuat, 3);
|
dim_impl!(UnitQuat, 3);
|
||||||
|
|
|
@ -11,6 +11,8 @@ use traits::operations::{Absolute, Inv, Transpose, ApproxEq};
|
||||||
use structs::vec::{Vec1, Vec2, Vec3, Vec4};
|
use structs::vec::{Vec1, Vec2, Vec3, Vec4};
|
||||||
use structs::pnt::{Pnt2, Pnt3, Pnt4};
|
use structs::pnt::{Pnt2, Pnt3, Pnt4};
|
||||||
use structs::mat::{Mat2, Mat3, Mat4, Mat5};
|
use structs::mat::{Mat2, Mat3, Mat4, Mat5};
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// Two dimensional rotation matrix.
|
/// Two dimensional rotation matrix.
|
||||||
|
@ -19,13 +21,13 @@ pub struct Rot2<N> {
|
||||||
submat: Mat2<N>
|
submat: Mat2<N>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Clone + BaseFloat + Neg<Output = N> + Copy> Rot2<N> {
|
impl<N: Clone + BaseFloat + Neg<Output = N>> Rot2<N> {
|
||||||
/// Builds a 2 dimensional rotation matrix from an angle in radian.
|
/// Builds a 2 dimensional rotation matrix from an angle in radian.
|
||||||
pub fn new(angle: Vec1<N>) -> Rot2<N> {
|
pub fn new(angle: Vec1<N>) -> Rot2<N> {
|
||||||
let (sia, coa) = angle.x.sin_cos();
|
let (sia, coa) = angle.x.sin_cos();
|
||||||
|
|
||||||
Rot2 {
|
Rot2 {
|
||||||
submat: Mat2::new(coa.clone(), -sia, sia.clone(), coa)
|
submat: Mat2::new(coa.clone(), -sia, sia, coa)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +89,14 @@ impl<N: BaseFloat> AbsoluteRotate<Vec2<N>> for Rot2<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + Clone + BaseFloat + Neg<Output = N>> Arbitrary for Rot2<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> Rot2<N> {
|
||||||
|
Rot2::new(Arbitrary::arbitrary(g))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 3d rotation
|
* 3d rotation
|
||||||
*/
|
*/
|
||||||
|
@ -287,6 +297,14 @@ impl<N: BaseFloat> AbsoluteRotate<Vec3<N>> for Rot3<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary + Clone + BaseFloat> Arbitrary for Rot3<N> {
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> Rot3<N> {
|
||||||
|
Rot3::new(Arbitrary::arbitrary(g))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Four dimensional rotation matrix.
|
/// Four dimensional rotation matrix.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Show, Hash, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Show, Hash, Copy)]
|
||||||
pub struct Rot4<N> {
|
pub struct Rot4<N> {
|
||||||
|
|
|
@ -14,6 +14,9 @@ use traits::structure::{Basis, Cast, Dim, Indexable, Iterable, IterableMut, VecA
|
||||||
NumVec, FloatVec, BaseFloat, BaseNum, Zero, One, Bounded};
|
NumVec, FloatVec, BaseFloat, BaseNum, Zero, One, Bounded};
|
||||||
use structs::pnt::{Pnt1, Pnt2, Pnt3, Pnt4, Pnt5, Pnt6};
|
use structs::pnt::{Pnt1, Pnt2, Pnt3, Pnt4, Pnt5, Pnt6};
|
||||||
|
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
|
|
||||||
|
|
||||||
/// Vector of dimension 0.
|
/// Vector of dimension 0.
|
||||||
#[derive(Eq, PartialEq, RustcDecodable, Clone, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcDecodable, Clone, Rand, Show, Copy)]
|
||||||
|
@ -81,6 +84,7 @@ transform_impl!(Vec1, Pnt1);
|
||||||
vec_as_pnt_impl!(Vec1, Pnt1, x);
|
vec_as_pnt_impl!(Vec1, Pnt1, x);
|
||||||
num_float_vec_impl!(Vec1);
|
num_float_vec_impl!(Vec1);
|
||||||
absolute_vec_impl!(Vec1, x);
|
absolute_vec_impl!(Vec1, x);
|
||||||
|
arbitrary_impl!(Vec1, x);
|
||||||
|
|
||||||
/// Vector of dimension 2.
|
/// Vector of dimension 2.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -132,6 +136,7 @@ transform_impl!(Vec2, Pnt2);
|
||||||
vec_as_pnt_impl!(Vec2, Pnt2, x, y);
|
vec_as_pnt_impl!(Vec2, Pnt2, x, y);
|
||||||
num_float_vec_impl!(Vec2);
|
num_float_vec_impl!(Vec2);
|
||||||
absolute_vec_impl!(Vec2, x, y);
|
absolute_vec_impl!(Vec2, x, y);
|
||||||
|
arbitrary_impl!(Vec2, x, y);
|
||||||
|
|
||||||
/// Vector of dimension 3.
|
/// Vector of dimension 3.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -185,6 +190,7 @@ transform_impl!(Vec3, Pnt3);
|
||||||
vec_as_pnt_impl!(Vec3, Pnt3, x, y, z);
|
vec_as_pnt_impl!(Vec3, Pnt3, x, y, z);
|
||||||
num_float_vec_impl!(Vec3);
|
num_float_vec_impl!(Vec3);
|
||||||
absolute_vec_impl!(Vec3, x, y, z);
|
absolute_vec_impl!(Vec3, x, y, z);
|
||||||
|
arbitrary_impl!(Vec3, x, y, z);
|
||||||
|
|
||||||
|
|
||||||
/// Vector of dimension 4.
|
/// Vector of dimension 4.
|
||||||
|
@ -241,6 +247,7 @@ transform_impl!(Vec4, Pnt4);
|
||||||
vec_as_pnt_impl!(Vec4, Pnt4, x, y, z, w);
|
vec_as_pnt_impl!(Vec4, Pnt4, x, y, z, w);
|
||||||
num_float_vec_impl!(Vec4);
|
num_float_vec_impl!(Vec4);
|
||||||
absolute_vec_impl!(Vec4, x, y, z, w);
|
absolute_vec_impl!(Vec4, x, y, z, w);
|
||||||
|
arbitrary_impl!(Vec4, x, y, z, w);
|
||||||
|
|
||||||
/// Vector of dimension 5.
|
/// Vector of dimension 5.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -298,6 +305,7 @@ transform_impl!(Vec5, Pnt5);
|
||||||
vec_as_pnt_impl!(Vec5, Pnt5, x, y, z, w, a);
|
vec_as_pnt_impl!(Vec5, Pnt5, x, y, z, w, a);
|
||||||
num_float_vec_impl!(Vec5);
|
num_float_vec_impl!(Vec5);
|
||||||
absolute_vec_impl!(Vec5, x, y, z, w, a);
|
absolute_vec_impl!(Vec5, x, y, z, w, a);
|
||||||
|
arbitrary_impl!(Vec5, x, y, z, w, a);
|
||||||
|
|
||||||
/// Vector of dimension 6.
|
/// Vector of dimension 6.
|
||||||
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Hash, Rand, Show, Copy)]
|
||||||
|
@ -355,3 +363,4 @@ transform_impl!(Vec6, Pnt6);
|
||||||
vec_as_pnt_impl!(Vec6, Pnt6, x, y, z, w, a, b);
|
vec_as_pnt_impl!(Vec6, Pnt6, x, y, z, w, a, b);
|
||||||
num_float_vec_impl!(Vec6);
|
num_float_vec_impl!(Vec6);
|
||||||
absolute_vec_impl!(Vec6, x, y, z, w, a, b);
|
absolute_vec_impl!(Vec6, x, y, z, w, a, b);
|
||||||
|
arbitrary_impl!(Vec6, x, y, z, w, a, b);
|
||||||
|
|
|
@ -844,3 +844,15 @@ macro_rules! absolute_vec_impl(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
macro_rules! arbitrary_impl(
|
||||||
|
($t: ident, $($compN: ident),*) => (
|
||||||
|
#[cfg(feature="arbitrary")]
|
||||||
|
impl<N: Arbitrary> Arbitrary for $t<N> {
|
||||||
|
#[inline]
|
||||||
|
fn arbitrary<G: Gen>(g: &mut G) -> $t<N> {
|
||||||
|
$t { $($compN: Arbitrary::arbitrary(g),)* }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
#![cfg(feature="arbitrary")]
|
||||||
|
|
||||||
|
extern crate "nalgebra" as na;
|
||||||
|
extern crate quickcheck;
|
||||||
|
|
||||||
|
use std::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!(Vec1<f64>, arb_vec1);
|
||||||
|
trivial_arb_test!(Vec2<f64>, arb_vec2);
|
||||||
|
trivial_arb_test!(Vec3<f64>, arb_vec3);
|
||||||
|
trivial_arb_test!(Vec4<f64>, arb_vec4);
|
||||||
|
trivial_arb_test!(Vec5<f64>, arb_vec5);
|
||||||
|
trivial_arb_test!(Vec6<f64>, arb_vec6);
|
||||||
|
|
||||||
|
trivial_arb_test!(Pnt1<f64>, arb_pnt1);
|
||||||
|
trivial_arb_test!(Pnt2<f64>, arb_pnt2);
|
||||||
|
trivial_arb_test!(Pnt3<f64>, arb_pnt3);
|
||||||
|
trivial_arb_test!(Pnt4<f64>, arb_pnt4);
|
||||||
|
trivial_arb_test!(Pnt5<f64>, arb_pnt5);
|
||||||
|
trivial_arb_test!(Pnt6<f64>, arb_pnt6);
|
||||||
|
|
||||||
|
trivial_arb_test!(Mat1<f64>, arb_mat1);
|
||||||
|
trivial_arb_test!(Mat2<f64>, arb_mat2);
|
||||||
|
trivial_arb_test!(Mat3<f64>, arb_mat3);
|
||||||
|
trivial_arb_test!(Mat4<f64>, arb_mat4);
|
||||||
|
trivial_arb_test!(Mat5<f64>, arb_mat5);
|
||||||
|
trivial_arb_test!(Mat6<f64>, arb_mat6);
|
||||||
|
|
||||||
|
trivial_arb_test!(DVec1<f64>, arb_dvec1);
|
||||||
|
trivial_arb_test!(DVec2<f64>, arb_dvec2);
|
||||||
|
trivial_arb_test!(DVec3<f64>, arb_dvec3);
|
||||||
|
trivial_arb_test!(DVec4<f64>, arb_dvec4);
|
||||||
|
trivial_arb_test!(DVec5<f64>, arb_dvec5);
|
||||||
|
trivial_arb_test!(DVec6<f64>, arb_dvec6);
|
||||||
|
|
||||||
|
trivial_arb_test!(DMat<f64>, arb_dmat);
|
||||||
|
trivial_arb_test!(DVec<f64>, arb_dvec);
|
||||||
|
|
||||||
|
trivial_arb_test!(Quat<f64>, arb_quat);
|
||||||
|
trivial_arb_test!(UnitQuat<f64>, arb_unit_quat);
|
||||||
|
|
||||||
|
trivial_arb_test!(Iso2<f64>, arb_iso2);
|
||||||
|
trivial_arb_test!(Iso3<f64>, arb_iso3);
|
||||||
|
|
||||||
|
trivial_arb_test!(Rot2<f64>, arb_rot2);
|
||||||
|
trivial_arb_test!(Rot3<f64>, arb_rot3);
|
||||||
|
|
||||||
|
trivial_arb_test!(Ortho3<f64>, arb_ortho3);
|
||||||
|
trivial_arb_test!(OrthoMat3<f64>, arb_ortho_mat3);
|
||||||
|
trivial_arb_test!(Persp3<f64>, arb_persp3);
|
||||||
|
trivial_arb_test!(PerspMat3<f64>, arb_persp_mat3);
|
Loading…
Reference in New Issue