commit
b7328f57b8
18 changed files with 37 additions and 36 deletions
|
@ -82,7 +82,7 @@ macro_rules! bench_unop(
|
||||||
);
|
);
|
||||||
|
|
||||||
macro_rules! bench_construction(
|
macro_rules! bench_construction(
|
||||||
($name: ident, $t: ty, $constructor: path $(, $args: ident: $types: ty)*) => {
|
($name: ident, $t: ty, $constructor: path [ $($args: ident: $types: ty),+ ]) => {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn $name(bh: &mut Bencher) {
|
fn $name(bh: &mut Bencher) {
|
||||||
const LEN: uint = 1 << 13;
|
const LEN: uint = 1 << 13;
|
||||||
|
|
|
@ -55,7 +55,7 @@ fn _bench_rot3_from_axisangle(bh: &mut Bencher) {
|
||||||
bench_from_axis_angle::<Basis3<f32>>(bh)
|
bench_from_axis_angle::<Basis3<f32>>(bh)
|
||||||
}
|
}
|
||||||
|
|
||||||
bench_construction!(_bench_rot2_from_axisangle, Basis2<f32>, Rotation2::from_angle, angle: Rad<f32>);
|
bench_construction!(_bench_rot2_from_axisangle, Basis2<f32>, Rotation2::from_angle [ angle: Rad<f32> ]);
|
||||||
|
|
||||||
bench_construction!(_bench_quat_from_euler_angles, Quaternion<f32>, Rotation3::from_euler, roll: Rad<f32>, pitch: Rad<f32>, yaw: Rad<f32>);
|
bench_construction!(_bench_quat_from_euler_angles, Quaternion<f32>, Rotation3::from_euler [roll: Rad<f32>, pitch: Rad<f32>, yaw: Rad<f32>]);
|
||||||
bench_construction!(_bench_rot3_from_euler_angles, Basis3<f32>, Rotation3::from_euler, roll: Rad<f32>, pitch: Rad<f32>, yaw: Rad<f32>);
|
bench_construction!(_bench_rot3_from_euler_angles, Basis3<f32>, Rotation3::from_euler [roll: Rad<f32>, pitch: Rad<f32>, yaw: Rad<f32>]);
|
||||||
|
|
|
@ -124,7 +124,7 @@ impl<S: BaseNum> Aabb<S, Vector2<S>, Point2<S>> for Aabb2<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Aabb2<S> {
|
impl<S: BaseNum> fmt::Show for Aabb2<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{} - {}]", self.min, self.max)
|
write!(f, "[{:?} - {:?}]", self.min, self.max)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ impl<S: BaseNum> Aabb<S, Vector3<S>, Point3<S>> for Aabb3<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Aabb3<S> {
|
impl<S: BaseNum> fmt::Show for Aabb3<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{} - {}]", self.min, self.max)
|
write!(f, "[{:?} - {:?}]", self.min, self.max)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -284,14 +284,14 @@ Angle<S> for Deg<S> {
|
||||||
impl<S: BaseFloat + fmt::Show>
|
impl<S: BaseFloat + fmt::Show>
|
||||||
fmt::Show for Rad<S> {
|
fmt::Show for Rad<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{} rad", self.s)
|
write!(f, "{:?} rad", self.s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: BaseFloat + fmt::Show>
|
impl<S: BaseFloat + fmt::Show>
|
||||||
fmt::Show for Deg<S> {
|
fmt::Show for Deg<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}°", self.s)
|
write!(f, "{:?}°", self.s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ macro_rules! assert_approx_eq_eps(
|
||||||
let eps = &($eps);
|
let eps = &($eps);
|
||||||
let (given_val, expected_val) = (&($given), &($expected));
|
let (given_val, expected_val) = (&($given), &($expected));
|
||||||
if !given_val.approx_eq_eps(expected_val, eps) {
|
if !given_val.approx_eq_eps(expected_val, eps) {
|
||||||
panic!("assertion failed: `left ≈ right` (left: `{}`, right: `{}`, tolerance: `{}`)",
|
panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)",
|
||||||
*given_val, *expected_val, *eps
|
*given_val, *expected_val, *eps
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ macro_rules! assert_approx_eq(
|
||||||
($given: expr, $expected: expr) => ({
|
($given: expr, $expected: expr) => ({
|
||||||
let (given_val, expected_val) = (&($given), &($expected));
|
let (given_val, expected_val) = (&($given), &($expected));
|
||||||
if !given_val.approx_eq(expected_val) {
|
if !given_val.approx_eq(expected_val) {
|
||||||
panic!("assertion failed: `left ≈ right` (left: `{}`, right: `{}`, tolerance: `{}`)",
|
panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)",
|
||||||
*given_val, *expected_val,
|
*given_val, *expected_val,
|
||||||
ApproxEq::approx_epsilon(Some(*given_val))
|
ApproxEq::approx_epsilon(Some(*given_val))
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#![crate_type = "rlib"]
|
#![crate_type = "rlib"]
|
||||||
#![crate_type = "dylib"]
|
#![crate_type = "dylib"]
|
||||||
|
#![feature(old_impl_check)]
|
||||||
|
|
||||||
//! Computer graphics-centric math.
|
//! Computer graphics-centric math.
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub struct Line<P> {
|
||||||
pub dest: P,
|
pub dest: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[old_impl_check]
|
||||||
impl<S: BaseNum, V: Vector<S>, P: Point<S, V>> Line<P> {
|
impl<S: BaseNum, V: Vector<S>, P: Point<S, V>> Line<P> {
|
||||||
pub fn new(origin: P, dest: P) -> Line<P> {
|
pub fn new(origin: P, dest: P) -> Line<P> {
|
||||||
Line { origin:origin, dest:dest }
|
Line { origin:origin, dest:dest }
|
||||||
|
|
|
@ -1383,7 +1383,7 @@ impl<S: BaseFloat + 'static> ToQuaternion<S> for Matrix3<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Matrix2<S> {
|
impl<S: BaseNum> fmt::Show for Matrix2<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[[{}, {}], [{}, {}]]",
|
write!(f, "[[{:?}, {:?}], [{:?}, {:?}]]",
|
||||||
self[0][0], self[0][1],
|
self[0][0], self[0][1],
|
||||||
self[1][0], self[1][1])
|
self[1][0], self[1][1])
|
||||||
}
|
}
|
||||||
|
@ -1391,7 +1391,7 @@ impl<S: BaseNum> fmt::Show for Matrix2<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Matrix3<S> {
|
impl<S: BaseNum> fmt::Show for Matrix3<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[[{}, {}, {}], [{}, {}, {}], [{}, {}, {}]]",
|
write!(f, "[[{:?}, {:?}, {:?}], [{:?}, {:?}, {:?}], [{:?}, {:?}, {:?}]]",
|
||||||
self[0][0], self[0][1], self[0][2],
|
self[0][0], self[0][1], self[0][2],
|
||||||
self[1][0], self[1][1], self[1][2],
|
self[1][0], self[1][1], self[1][2],
|
||||||
self[2][0], self[2][1], self[2][2])
|
self[2][0], self[2][1], self[2][2])
|
||||||
|
@ -1400,7 +1400,7 @@ impl<S: BaseNum> fmt::Show for Matrix3<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Matrix4<S> {
|
impl<S: BaseNum> fmt::Show for Matrix4<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[[{}, {}, {}, {}], [{}, {}, {}, {}], [{}, {}, {}, {}], [{}, {}, {}, {}]]",
|
write!(f, "[[{:?}, {:?}, {:?}, {:?}], [{:?}, {:?}, {:?}, {:?}], [{:?}, {:?}, {:?}, {:?}], [{:?}, {:?}, {:?}, {:?}]]",
|
||||||
self[0][0], self[0][1], self[0][2], self[0][3],
|
self[0][0], self[0][1], self[0][2], self[0][3],
|
||||||
self[1][0], self[1][1], self[1][2], self[1][3],
|
self[1][0], self[1][1], self[1][2], self[1][3],
|
||||||
self[2][0], self[2][1], self[2][2], self[2][3],
|
self[2][0], self[2][1], self[2][2], self[2][3],
|
||||||
|
|
|
@ -131,7 +131,7 @@ ApproxEq<S> for Plane<S> {
|
||||||
|
|
||||||
impl<S: BaseFloat> fmt::Show for Plane<S> {
|
impl<S: BaseFloat> fmt::Show for Plane<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}x + {}y + {}z - {} = 0",
|
write!(f, "{:?}x + {:?}y + {:?}z - {:?} = 0",
|
||||||
self.n.x, self.n.y, self.n.z, self.d)
|
self.n.x, self.n.y, self.n.z, self.d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -433,12 +433,12 @@ impl<S: BaseFloat> ApproxEq<S> for Point3<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Point2<S> {
|
impl<S: BaseNum> fmt::Show for Point2<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{}, {}]", self.x, self.y)
|
write!(f, "[{:?}, {:?}]", self.x, self.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Point3<S> {
|
impl<S: BaseNum> fmt::Show for Point3<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{}, {}, {}]", self.x, self.y, self.z)
|
write!(f, "[{:?}, {:?}, {:?}]", self.x, self.y, self.z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,12 +105,12 @@ impl<S: BaseFloat, A: Angle<S>> ToMatrix4<S> for PerspectiveFov<S, A> {
|
||||||
fn to_matrix4(&self) -> Matrix4<S> {
|
fn to_matrix4(&self) -> Matrix4<S> {
|
||||||
let half_turn: A = Angle::turn_div_2();
|
let half_turn: A = Angle::turn_div_2();
|
||||||
|
|
||||||
assert!(self.fovy > zero(), "The vertical field of view cannot be below zero, found: {}", self.fovy);
|
assert!(self.fovy > zero(), "The vertical field of view cannot be below zero, found: {:?}", self.fovy);
|
||||||
assert!(self.fovy < half_turn, "The vertical field of view cannot be greater than a half turn, found: {}", self.fovy);
|
assert!(self.fovy < half_turn, "The vertical field of view cannot be greater than a half turn, found: {:?}", self.fovy);
|
||||||
assert!(self.aspect > zero(), "The aspect ratio cannot be below zero, found: {}", self.aspect);
|
assert!(self.aspect > zero(), "The aspect ratio cannot be below zero, found: {:?}", self.aspect);
|
||||||
assert!(self.near > zero(), "The near plane distance cannot be below zero, found: {}", self.near);
|
assert!(self.near > zero(), "The near plane distance cannot be below zero, found: {:?}", self.near);
|
||||||
assert!(self.far > zero(), "The far plane distance cannot be below zero, found: {}", self.far);
|
assert!(self.far > zero(), "The far plane distance cannot be below zero, found: {:?}", self.far);
|
||||||
assert!(self.far > self.near, "The far plane cannot be closer than the near plane, found: far: {}, near: {}", self.far, self.near);
|
assert!(self.far > self.near, "The far plane cannot be closer than the near plane, found: far: {:?}, near: {:?}", self.far, self.near);
|
||||||
|
|
||||||
let f = cot(self.fovy.div_s(cast(2i).unwrap()).to_rad());
|
let f = cot(self.fovy.div_s(cast(2i).unwrap()).to_rad());
|
||||||
let two: S = cast(2i).unwrap();
|
let two: S = cast(2i).unwrap();
|
||||||
|
@ -159,9 +159,9 @@ impl<S: BaseFloat + 'static> Projection<S> for Perspective<S> {
|
||||||
|
|
||||||
impl<S: BaseFloat + 'static> ToMatrix4<S> for Perspective<S> {
|
impl<S: BaseFloat + 'static> ToMatrix4<S> for Perspective<S> {
|
||||||
fn to_matrix4(&self) -> Matrix4<S> {
|
fn to_matrix4(&self) -> Matrix4<S> {
|
||||||
assert!(self.left <= self.right, "`left` cannot be greater than `right`, found: left: {} right: {}", self.left, self.right);
|
assert!(self.left <= self.right, "`left` cannot be greater than `right`, found: left: {:?} right: {:?}", self.left, self.right);
|
||||||
assert!(self.bottom <= self.top, "`bottom` cannot be greater than `top`, found: bottom: {} top: {}", self.bottom, self.top);
|
assert!(self.bottom <= self.top, "`bottom` cannot be greater than `top`, found: bottom: {:?} top: {:?}", self.bottom, self.top);
|
||||||
assert!(self.near <= self.far, "`near` cannot be greater than `far`, found: near: {} far: {}", self.near, self.far);
|
assert!(self.near <= self.far, "`near` cannot be greater than `far`, found: near: {:?} far: {:?}", self.near, self.far);
|
||||||
|
|
||||||
let two: S = cast(2i).unwrap();
|
let two: S = cast(2i).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -372,7 +372,7 @@ impl<S: BaseFloat> Neg for Quaternion<S> {
|
||||||
|
|
||||||
impl<S: BaseFloat> fmt::Show for Quaternion<S> {
|
impl<S: BaseFloat> fmt::Show for Quaternion<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{} + {}i + {}j + {}k",
|
write!(f, "{:?} + {:?}i + {:?}j + {:?}k",
|
||||||
self.s,
|
self.s,
|
||||||
self.v.x,
|
self.v.x,
|
||||||
self.v.y,
|
self.v.y,
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub struct Ray<P,V> {
|
||||||
pub direction: V,
|
pub direction: V,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[old_impl_check]
|
||||||
impl<S: BaseNum, V: Vector<S>, P: Point<S, V>> Ray<P, V> {
|
impl<S: BaseNum, V: Vector<S>, P: Point<S, V>> Ray<P, V> {
|
||||||
pub fn new(origin: P, direction: V) -> Ray<P,V> {
|
pub fn new(origin: P, direction: V) -> Ray<P,V> {
|
||||||
Ray { origin: origin, direction: direction }
|
Ray { origin: origin, direction: direction }
|
||||||
|
|
|
@ -132,7 +132,7 @@ pub trait Rotation3<S: BaseNum>: Rotation<S, Vector3<S>, Point3<S>>
|
||||||
/// angle. We can accomplish this quite easily with a two-dimensional rotation
|
/// angle. We can accomplish this quite easily with a two-dimensional rotation
|
||||||
/// matrix:
|
/// matrix:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ~~~ignore
|
||||||
/// use cgmath::rad;
|
/// use cgmath::rad;
|
||||||
/// use cgmath::Vector2;
|
/// use cgmath::Vector2;
|
||||||
/// use cgmath::{Matrix, ToMatrix2};
|
/// use cgmath::{Matrix, ToMatrix2};
|
||||||
|
@ -160,7 +160,7 @@ pub trait Rotation3<S: BaseNum>: Rotation<S, Vector3<S>, Point3<S>>
|
||||||
/// let rot_half: Basis2<f64> = Rotation2::from_angle(rad(0.25f64 * f64::consts::PI));
|
/// let rot_half: Basis2<f64> = Rotation2::from_angle(rad(0.25f64 * f64::consts::PI));
|
||||||
/// let unit_y3 = rot_half.concat(&rot_half).rotate_vector(&unit_x);
|
/// let unit_y3 = rot_half.concat(&rot_half).rotate_vector(&unit_x);
|
||||||
/// assert!(unit_y3.approx_eq(&unit_y2));
|
/// assert!(unit_y3.approx_eq(&unit_y2));
|
||||||
/// ```
|
/// ~~~
|
||||||
#[derive(PartialEq, Copy, Clone, RustcEncodable, RustcDecodable)]
|
#[derive(PartialEq, Copy, Clone, RustcEncodable, RustcDecodable)]
|
||||||
pub struct Basis2<S> {
|
pub struct Basis2<S> {
|
||||||
mat: Matrix2<S>
|
mat: Matrix2<S>
|
||||||
|
|
|
@ -153,7 +153,7 @@ impl<S: BaseFloat, R: Rotation3<S>> Transform3<S> for Decomposed<S,Vector3<S>,R>
|
||||||
|
|
||||||
impl<S: BaseFloat, R: fmt::Show + Rotation3<S>> fmt::Show for Decomposed<S,Vector3<S>,R> {
|
impl<S: BaseFloat, R: fmt::Show + Rotation3<S>> fmt::Show for Decomposed<S,Vector3<S>,R> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "(scale({}), rot({}), disp{})",
|
write!(f, "(scale({:?}), rot({:?}), disp{:?})",
|
||||||
self.scale, self.rot, self.disp)
|
self.scale, self.rot, self.disp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,7 +457,7 @@ impl<S: BaseNum> Vector4<S> {
|
||||||
1 => Vector3::new(self.x, self.z, self.w),
|
1 => Vector3::new(self.x, self.z, self.w),
|
||||||
2 => Vector3::new(self.x, self.y, self.w),
|
2 => Vector3::new(self.x, self.y, self.w),
|
||||||
3 => Vector3::new(self.x, self.y, self.z),
|
3 => Vector3::new(self.x, self.y, self.z),
|
||||||
_ => panic!("{} is out of range", n)
|
_ => panic!("{:?} is out of range", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,18 +555,18 @@ impl<S: BaseFloat> EuclideanVector<S> for Vector4<S> {
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Vector2<S> {
|
impl<S: BaseNum> fmt::Show for Vector2<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{}, {}]", self.x, self.y)
|
write!(f, "[{:?}, {:?}]", self.x, self.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Vector3<S> {
|
impl<S: BaseNum> fmt::Show for Vector3<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{}, {}, {}]", self.x, self.y, self.z)
|
write!(f, "[{:?}, {:?}, {:?}]", self.x, self.y, self.z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: BaseNum> fmt::Show for Vector4<S> {
|
impl<S: BaseNum> fmt::Show for Vector4<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "[{}, {}, {}, {}]", self.x, self.y, self.z, self.w)
|
write!(f, "[{:?}, {:?}, {:?}, {:?}]", self.x, self.y, self.z, self.w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,6 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
#![feature(phase)]
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate cgmath;
|
extern crate cgmath;
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ fn to_and_from_quaternion()
|
||||||
if !(ax.approx_eq_eps(&bx, &0.001) &&
|
if !(ax.approx_eq_eps(&bx, &0.001) &&
|
||||||
ay.approx_eq_eps(&by, &0.001) &&
|
ay.approx_eq_eps(&by, &0.001) &&
|
||||||
az.approx_eq_eps(&bz, &0.001)) {
|
az.approx_eq_eps(&bz, &0.001)) {
|
||||||
panic!("{} != {}", a, b)
|
panic!("{:?} != {:?}", a, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue