From 3bb470ac957d07d0b9826e4a5486e32070dbbffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 29 Jun 2013 11:40:31 +0000 Subject: [PATCH] Add Column + Homogeneous + Indexable traits. Column: to access a matrix column. Homogeneous: to convert a matrix/vector from/to homogenous coordinates. Indexable: to access a matrix/vector element using indices. --- src/adaptors/rotmat.rs | 11 +++- src/adaptors/transform.rs | 28 +++++++++ src/mat.rs | 47 ++++++++++----- src/mat_impl.rs | 124 ++++++++++++++++++++++++++++++-------- src/nalgebra.rc | 3 + src/traits/column.rs | 5 ++ src/traits/homogeneous.rs | 9 +++ src/traits/indexable.rs | 9 +++ src/vec.rs | 24 ++++++++ src/vec_impl.rs | 64 ++++++++++++++++++++ 10 files changed, 281 insertions(+), 43 deletions(-) create mode 100644 src/traits/column.rs create mode 100644 src/traits/homogeneous.rs create mode 100644 src/traits/indexable.rs diff --git a/src/adaptors/rotmat.rs b/src/adaptors/rotmat.rs index 87a0ff26..42355585 100644 --- a/src/adaptors/rotmat.rs +++ b/src/adaptors/rotmat.rs @@ -9,6 +9,8 @@ use traits::inv::Inv; use traits::transpose::Transpose; use traits::rotation::{Rotation, Rotate, Rotatable}; use traits::transformation::{Transform}; // FIXME: implement Transformation and Transformable +use traits::homogeneous::ToHomogeneous; +use traits::indexable::Indexable; use vec::Vec1; use mat::{Mat2, Mat3}; use vec::Vec3; @@ -61,7 +63,7 @@ Rotation> for Rotmat> { #[inline] fn rotation(&self) -> Vec1 - { Vec1::new([ -(self.submat.at(0, 1) / self.submat.at(0, 0)).atan() ]) } + { Vec1::new([ -(self.submat.at((0, 1)) / self.submat.at((0, 0))).atan() ]) } #[inline] fn inv_rotation(&self) -> Vec1 @@ -200,6 +202,13 @@ Transpose for Rotmat { self.submat.transpose() } } +// we loose the info that we are a rotation matrix +impl, M2> ToHomogeneous for Rotmat +{ + fn to_homogeneous(&self) -> M2 + { self.submat.to_homogeneous() } +} + impl, M: ApproxEq> ApproxEq for Rotmat { #[inline] diff --git a/src/adaptors/transform.rs b/src/adaptors/transform.rs index cc7de693..3aa2d3c8 100644 --- a/src/adaptors/transform.rs +++ b/src/adaptors/transform.rs @@ -8,6 +8,8 @@ use traits::translation::{Translation, Translate, Translatable}; use traits::transformation; use traits::transformation::{Transformation, Transformable}; use traits::rlmul::{RMul, LMul}; +use traits::homogeneous::{ToHomogeneous, FromHomogeneous}; +use traits::column::Column; #[deriving(Eq, ToStr)] pub struct Transform @@ -215,6 +217,32 @@ Inv for Transform } } +impl, M2: Dim + Column, V: Copy> +ToHomogeneous for Transform +{ + fn to_homogeneous(&self) -> M2 + { + let mut res = self.submat.to_homogeneous(); + + // copy the translation + let dim = Dim::dim::(); + + res.set_column(dim - 1, copy self.subtrans); + + res + } +} + +impl + Dim, M2: FromHomogeneous, V: Copy> +FromHomogeneous for Transform +{ + fn from_homogeneous(m: &M) -> Transform + { + Transform::new(FromHomogeneous::from_homogeneous(m), + m.column(Dim::dim::() - 1)) + } +} + impl, M:ApproxEq, V:ApproxEq> ApproxEq for Transform { diff --git a/src/mat.rs b/src/mat.rs index 8286f9e3..a8d7eb3e 100644 --- a/src/mat.rs +++ b/src/mat.rs @@ -12,6 +12,10 @@ use traits::division_ring::DivisionRing; use traits::transpose::Transpose; use traits::rlmul::{RMul, LMul}; use traits::transformation::Transform; +use traits::homogeneous::{ToHomogeneous, FromHomogeneous}; +use traits::indexable::Indexable; +use traits::column::Column; +use traits::iterable::{Iterable, IterableMut}; mod mat_impl; @@ -21,9 +25,9 @@ pub struct Mat1 mat_impl!(Mat1, 1) one_impl!(Mat1, [ _1 ]) -zero_impl!(Mat1, [ _0 ]) +zero_impl!(Mat1, [ _0 ]) dim_impl!(Mat1, 1) -mat_indexing_impl!(Mat1, 1) +mat_indexable_impl!(Mat1, 1) mul_impl!(Mat1, 1) rmul_impl!(Mat1, Vec1, 1) lmul_impl!(Mat1, Vec1, 1) @@ -32,6 +36,9 @@ transform_impl!(Mat1, Vec1) transpose_impl!(Mat1, 1) approx_eq_impl!(Mat1) rand_impl!(Mat1, rng, [ rng ]) +to_homogeneous_impl!(Mat1, Mat2, 1) +from_homogeneous_impl!(Mat2, Mat1, 1) +column_impl!(Mat2, 2) #[deriving(ToStr)] pub struct Mat2 @@ -39,11 +46,11 @@ pub struct Mat2 mat_impl!(Mat2, 2) one_impl!(Mat2, [ _1 | _0 | - _0 | _1 ]) + _0 | _1 ]) zero_impl!(Mat2, [ _0 | _0 | - _0 | _0 ]) + _0 | _0 ]) dim_impl!(Mat2, 2) -mat_indexing_impl!(Mat2, 2) +mat_indexable_impl!(Mat2, 2) mul_impl!(Mat2, 2) rmul_impl!(Mat2, Vec2, 2) lmul_impl!(Mat2, Vec2, 2) @@ -52,7 +59,9 @@ transform_impl!(Mat2, Vec2) transpose_impl!(Mat2, 2) approx_eq_impl!(Mat2) rand_impl!(Mat2, rng, [ rng | rng | - rng | rng ]) + rng | rng ]) +to_homogeneous_impl!(Mat2, Mat3, 2) +from_homogeneous_impl!(Mat3, Mat2, 2) #[deriving(ToStr)] pub struct Mat3 @@ -60,13 +69,13 @@ pub struct Mat3 mat_impl!(Mat3, 3) one_impl!(Mat3, [ _1 | _0 | _0 | - _0 | _1 | _0 | - _0 | _0 | _1 ]) + _0 | _1 | _0 | + _0 | _0 | _1 ]) zero_impl!(Mat3, [ _0 | _0 | _0 | - _0 | _0 | _0 | - _0 | _0 | _0 ]) + _0 | _0 | _0 | + _0 | _0 | _0 ]) dim_impl!(Mat3, 3) -mat_indexing_impl!(Mat3, 3) +mat_indexable_impl!(Mat3, 3) mul_impl!(Mat3, 3) rmul_impl!(Mat3, Vec3, 3) lmul_impl!(Mat3, Vec3, 3) @@ -75,8 +84,10 @@ transform_impl!(Mat3, Vec3) transpose_impl!(Mat3, 3) approx_eq_impl!(Mat3) rand_impl!(Mat3, rng, [ rng | rng | rng | - rng | rng | rng | - rng | rng | rng]) + rng | rng | rng | + rng | rng | rng]) +to_homogeneous_impl!(Mat3, Mat4, 3) +from_homogeneous_impl!(Mat4, Mat3, 3) #[deriving(ToStr)] pub struct Mat4 @@ -96,7 +107,7 @@ zero_impl!(Mat4, [ _0 | _0 | _0 | _0 ]) dim_impl!(Mat4, 4) -mat_indexing_impl!(Mat4, 4) +mat_indexable_impl!(Mat4, 4) mul_impl!(Mat4, 4) rmul_impl!(Mat4, Vec4, 4) lmul_impl!(Mat4, Vec4, 4) @@ -110,6 +121,8 @@ rand_impl!(Mat4, rng, [ rng | rng | rng | rng | rng | rng | rng | rng ]) +to_homogeneous_impl!(Mat4, Mat5, 4) +from_homogeneous_impl!(Mat5, Mat4, 4) #[deriving(ToStr)] pub struct Mat5 @@ -131,7 +144,7 @@ zero_impl!(Mat5, [ _0 | _0 | _0 | _0 | _0 ]) dim_impl!(Mat5, 5) -mat_indexing_impl!(Mat5, 5) +mat_indexable_impl!(Mat5, 5) mul_impl!(Mat5, 5) rmul_impl!(Mat5, Vec5, 5) lmul_impl!(Mat5, Vec5, 5) @@ -146,6 +159,8 @@ rand_impl!(Mat5, rng, [ rng | rng | rng | rng | rng | rng | rng | rng | rng | rng ]) +to_homogeneous_impl!(Mat5, Mat6, 5) +from_homogeneous_impl!(Mat6, Mat5, 5) #[deriving(ToStr)] pub struct Mat6 @@ -169,7 +184,7 @@ zero_impl!(Mat6, [ _0 | _0 | _0 | _0 | _0 | _0 ]) dim_impl!(Mat6, 6) -mat_indexing_impl!(Mat6, 6) +mat_indexable_impl!(Mat6, 6) mul_impl!(Mat6, 6) rmul_impl!(Mat6, Vec6, 6) lmul_impl!(Mat6, Vec6, 6) diff --git a/src/mat_impl.rs b/src/mat_impl.rs index f0bfdde6..ff597e79 100644 --- a/src/mat_impl.rs +++ b/src/mat_impl.rs @@ -7,6 +7,10 @@ macro_rules! mat_impl( #[inline] pub fn new(mij: [N, ..$dim * $dim]) -> $t { $t { mij: mij } } + + #[inline] + pub fn offset(&self, i: uint, j: uint) -> uint + { i * $dim + j } } ) ) @@ -54,24 +58,49 @@ macro_rules! dim_impl( ) ) -macro_rules! mat_indexing_impl( +macro_rules! mat_indexable_impl( ($t: ident, $dim: expr) => ( - impl $t + impl Indexable<(uint, uint), N> for $t { #[inline] - pub fn offset(&self, i: uint, j: uint) -> uint - { i * $dim + j } - + pub fn at(&self, (i, j): (uint, uint)) -> N + { copy self.mij[self.offset(i, j)] } + #[inline] - pub fn set(&mut self, i: uint, j: uint, t: &N) + pub fn set(&mut self, (i, j): (uint, uint), t: N) + { self.mij[self.offset(i, j)] = t } + } + ) +) + +macro_rules! column_impl( + ($t: ident, $dim: expr) => ( + impl + IterableMut> Column for $t + { + fn set_column(&mut self, col: uint, v: V) { - self.mij[self.offset(i, j)] = copy *t + for v.iter().enumerate().advance |(i, e)| + { + if i == Dim::dim::<$t>() + { break } + + self.at((i, col)) = copy *e; + } } - - #[inline] - pub fn at(&self, i: uint, j: uint) -> N + + fn column(&self, col: uint) -> V { - copy self.mij[self.offset(i, j)] + let mut res = Zero::zero::(); + + for res.mut_iter().enumerate().advance |(i, e)| + { + if i >= Dim::dim::<$t>() + { break } + + *e = self.at((i, col)); + } + + res } } ) @@ -93,9 +122,9 @@ macro_rules! mul_impl( let mut acc = Zero::zero::(); for iterate(0u, $dim) |k| - { acc = acc + self.at(i, k) * other.at(k, j); } + { acc = acc + self.at((i, k)) * other.at((k, j)); } - res.set(i, j, &acc); + res.set((i, j), acc); } } @@ -117,7 +146,7 @@ macro_rules! rmul_impl( for iterate(0u, $dim) |i| { for iterate(0u, $dim) |j| - { res.at[i] = res.at[i] + other.at[j] * self.at(i, j); } + { res.at[i] = res.at[i] + other.at[j] * self.at((i, j)); } } res @@ -139,7 +168,7 @@ macro_rules! lmul_impl( for iterate(0u, $dim) |i| { for iterate(0u, $dim) |j| - { res.at[i] = res.at[i] + other.at[j] * self.at(j, i); } + { res.at[i] = res.at[i] + other.at[j] * self.at((j, i)); } } res @@ -195,7 +224,7 @@ macro_rules! inv_impl( while (n0 != $dim) { - if self.at(n0, k) != _0N + if self.at((n0, k)) != _0N { break; } n0 = n0 + 1; @@ -214,36 +243,36 @@ macro_rules! inv_impl( } } - let pivot = self.at(k, k); + let pivot = self.at((k, k)); for iterate(k, $dim) |j| { - let selfval = &(self.at(k, j) / pivot); - self.set(k, j, selfval); + let selfval = self.at((k, j)) / pivot; + self.set((k, j), selfval); } for iterate(0u, $dim) |j| { - let resval = &(res.at(k, j) / pivot); - res.set(k, j, resval); + let resval = res.at((k, j)) / pivot; + res.set((k, j), resval); } for iterate(0u, $dim) |l| { if l != k { - let normalizer = self.at(l, k); + let normalizer = self.at((l, k)); for iterate(k, $dim) |j| { - let selfval = &(self.at(l, j) - self.at(k, j) * normalizer); - self.set(l, j, selfval); + let selfval = self.at((l, j)) - self.at((k, j)) * normalizer; + self.set((l, j), selfval); } for iterate(0u, $dim) |j| { - let resval = &(res.at(l, j) - res.at(k, j) * normalizer); - res.set(l, j, resval); + let resval = res.at((l, j)) - res.at((k, j)) * normalizer; + res.set((l, j), resval); } } } @@ -323,3 +352,46 @@ macro_rules! rand_impl( } ) ) + +macro_rules! to_homogeneous_impl( + ($t: ident, $t2: ident, $dim: expr) => ( + impl ToHomogeneous<$t2> for $t + { + fn to_homogeneous(&self) -> $t2 + { + let mut res: $t2 = One::one(); + + for iterate(0, $dim) |i| + { + for iterate(0, $dim) |j| + { res.set((i, j), self.at((i, j))) } + } + + res + } + } + ) +) + +macro_rules! from_homogeneous_impl( + ($t: ident, $t2: ident, $dim2: expr) => ( + impl FromHomogeneous<$t2> for $t + { + fn from_homogeneous(m: &$t2) -> $t + { + let mut res: $t = One::one(); + + for iterate(0, $dim2) |i| + { + for iterate(0, $dim2) |j| + { res.set((i, j), m.at((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 + } + } + ) +) diff --git a/src/nalgebra.rc b/src/nalgebra.rc index afbcc79a..bd3e6129 100644 --- a/src/nalgebra.rc +++ b/src/nalgebra.rc @@ -34,6 +34,8 @@ pub mod adaptors /// Useful linear-algebra related traits. pub mod traits { + pub mod indexable; + pub mod column; pub mod iterable; pub mod dot; pub mod cross; @@ -51,6 +53,7 @@ pub mod traits pub mod sub_dot; pub mod rlmul; pub mod scalar_op; + pub mod homogeneous; } #[cfg(test)] diff --git a/src/traits/column.rs b/src/traits/column.rs new file mode 100644 index 00000000..25b69e1f --- /dev/null +++ b/src/traits/column.rs @@ -0,0 +1,5 @@ +pub trait Column +{ + fn set_column(&mut self, uint, C); + fn column(&self, uint) -> C; +} diff --git a/src/traits/homogeneous.rs b/src/traits/homogeneous.rs new file mode 100644 index 00000000..4da097d9 --- /dev/null +++ b/src/traits/homogeneous.rs @@ -0,0 +1,9 @@ +pub trait ToHomogeneous +{ + fn to_homogeneous(&self) -> U; +} + +pub trait FromHomogeneous +{ + fn from_homogeneous(&U) -> Self; +} diff --git a/src/traits/indexable.rs b/src/traits/indexable.rs new file mode 100644 index 00000000..98b5a3aa --- /dev/null +++ b/src/traits/indexable.rs @@ -0,0 +1,9 @@ +// FIXME: this trait should not be on nalgebra. +// however, it is needed because std::ops::Index is (strangely) to poor: it +// does not have a function to set values. +// Also, using Index with tuples crashes. +pub trait Indexable +{ + fn at(&self, Index) -> Res; + fn set(&mut self, Index, Res); +} diff --git a/src/vec.rs b/src/vec.rs index 9ba016b0..26881efb 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -14,6 +14,8 @@ use traits::translation::{Translation, Translatable}; use traits::scalar_op::{ScalarMul, ScalarDiv, ScalarAdd, ScalarSub}; use traits::ring::Ring; use traits::division_ring::DivisionRing; +use traits::homogeneous::{ToHomogeneous, FromHomogeneous}; +use traits::indexable::Indexable; mod vec_impl; @@ -24,6 +26,7 @@ pub struct Vec1 new_impl!(Vec1, 1) new_repeat_impl!(Vec1, elem, [elem]) +indexable_impl!(Vec1) dim_impl!(Vec1, 1) eq_impl!(Vec1) // (specialized) basis_impl!(Vec1, 1) @@ -41,12 +44,15 @@ translatable_impl!(Vec1) norm_impl!(Vec1, 1) approx_eq_impl!(Vec1) zero_impl!(Vec1) +one_impl!(Vec1) rand_impl!(Vec1, rng, [rng]) from_iterator_impl!(Vec1, iterator, [iterator]) from_any_iterator_impl!(Vec1, iterator, [iterator]) bounded_impl!(Vec1) iterable_impl!(Vec1) iterable_mut_impl!(Vec1) +to_homogeneous_impl!(Vec1, Vec2) +from_homogeneous_impl!(Vec2, Vec1, 2) #[deriving(Ord, ToStr)] pub struct Vec2 @@ -54,6 +60,7 @@ pub struct Vec2 new_impl!(Vec2, 2) new_repeat_impl!(Vec2, elem, [elem | elem]) +indexable_impl!(Vec2) dim_impl!(Vec2, 2) eq_impl!(Vec2) // (specialized) basis_impl!(Vec2, 2) @@ -71,12 +78,15 @@ translatable_impl!(Vec2) norm_impl!(Vec2, 2) approx_eq_impl!(Vec2) zero_impl!(Vec2) +one_impl!(Vec2) rand_impl!(Vec2, rng, [rng | rng]) from_iterator_impl!(Vec2, iterator, [iterator | iterator]) from_any_iterator_impl!(Vec2, iterator, [iterator | iterator]) bounded_impl!(Vec2) iterable_impl!(Vec2) iterable_mut_impl!(Vec2) +to_homogeneous_impl!(Vec2, Vec3) +from_homogeneous_impl!(Vec3, Vec2, 3) #[deriving(Ord, ToStr)] pub struct Vec3 @@ -84,6 +94,7 @@ pub struct Vec3 new_impl!(Vec3, 3) new_repeat_impl!(Vec3, elem, [elem | elem | elem]) +indexable_impl!(Vec3) dim_impl!(Vec3, 3) eq_impl!(Vec3) // (specialized) basis_impl!(Vec3, 3) @@ -101,12 +112,15 @@ translatable_impl!(Vec3) norm_impl!(Vec3, 3) approx_eq_impl!(Vec3) zero_impl!(Vec3) +one_impl!(Vec3) rand_impl!(Vec3, rng, [rng | rng | rng]) from_iterator_impl!(Vec3, iterator, [iterator | iterator | iterator]) from_any_iterator_impl!(Vec3, iterator, [iterator | iterator | iterator]) bounded_impl!(Vec3) iterable_impl!(Vec3) iterable_mut_impl!(Vec3) +to_homogeneous_impl!(Vec3, Vec4) +from_homogeneous_impl!(Vec4, Vec3, 4) #[deriving(Ord, ToStr)] pub struct Vec4 @@ -114,6 +128,7 @@ pub struct Vec4 new_impl!(Vec4, 4) new_repeat_impl!(Vec4, elem, [elem | elem | elem | elem]) +indexable_impl!(Vec4) dim_impl!(Vec4, 4) eq_impl!(Vec4) basis_impl!(Vec4, 4) @@ -131,12 +146,15 @@ translatable_impl!(Vec4) norm_impl!(Vec4, 4) approx_eq_impl!(Vec4) zero_impl!(Vec4) +one_impl!(Vec4) rand_impl!(Vec4, rng, [rng | rng | rng | rng]) from_iterator_impl!(Vec4, iterator, [iterator | iterator | iterator | iterator]) from_any_iterator_impl!(Vec4, iterator, [iterator | iterator | iterator | iterator]) bounded_impl!(Vec4) iterable_impl!(Vec4) iterable_mut_impl!(Vec4) +to_homogeneous_impl!(Vec4, Vec5) +from_homogeneous_impl!(Vec5, Vec4, 5) #[deriving(Ord, ToStr)] pub struct Vec5 @@ -144,6 +162,7 @@ pub struct Vec5 new_impl!(Vec5, 5) new_repeat_impl!(Vec5, elem, [elem | elem | elem | elem | elem]) +indexable_impl!(Vec5) dim_impl!(Vec5, 5) eq_impl!(Vec5) basis_impl!(Vec5, 5) @@ -161,12 +180,15 @@ translatable_impl!(Vec5) norm_impl!(Vec5, 5) approx_eq_impl!(Vec5) zero_impl!(Vec5) +one_impl!(Vec5) rand_impl!(Vec5, rng, [rng | rng | rng | rng | rng]) from_iterator_impl!(Vec5, iterator, [iterator | iterator | iterator | iterator | iterator]) from_any_iterator_impl!(Vec5, iterator, [iterator | iterator | iterator | iterator | iterator]) bounded_impl!(Vec5) iterable_impl!(Vec5) iterable_mut_impl!(Vec5) +to_homogeneous_impl!(Vec5, Vec6) +from_homogeneous_impl!(Vec6, Vec5, 6) #[deriving(Ord, ToStr)] pub struct Vec6 @@ -174,6 +196,7 @@ pub struct Vec6 new_impl!(Vec6, 6) new_repeat_impl!(Vec6, elem, [elem | elem | elem | elem | elem | elem]) +indexable_impl!(Vec6) dim_impl!(Vec6, 6) eq_impl!(Vec6) basis_impl!(Vec6, 6) @@ -191,6 +214,7 @@ translatable_impl!(Vec6) norm_impl!(Vec6, 6) approx_eq_impl!(Vec6) zero_impl!(Vec6) +one_impl!(Vec6) rand_impl!(Vec6, rng, [rng | rng | rng | rng | rng | rng]) from_iterator_impl!(Vec6, iterator, [iterator | iterator | iterator | iterator | iterator | iterator]) from_any_iterator_impl!(Vec6, iterator, [iterator | iterator | iterator | iterator | iterator | iterator]) diff --git a/src/vec_impl.rs b/src/vec_impl.rs index 787dbe49..37f3902a 100644 --- a/src/vec_impl.rs +++ b/src/vec_impl.rs @@ -11,6 +11,21 @@ macro_rules! new_impl( ) ) +macro_rules! indexable_impl( + ($t: ident) => ( + impl Indexable for $t + { + #[inline] + pub fn at(&self, i: uint) -> N + { copy self.at[i] } + + #[inline] + pub fn set(&mut self, i: uint, val: N) + { self.at[i] = val } + } + ) +) + macro_rules! new_repeat_impl( ($t: ident, $param: ident, [ $($elem: ident)|+ ]) => ( impl $t @@ -381,6 +396,17 @@ macro_rules! zero_impl( ) ) +macro_rules! one_impl( + ($t: ident) => ( + impl One for $t + { + #[inline] + fn one() -> $t + { $t::new_repeat(One::one()) } + } + ) +) + macro_rules! rand_impl( ($t: ident, $param: ident, [ $($elem: ident)|+ ]) => ( impl Rand for $t @@ -429,3 +455,41 @@ macro_rules! bounded_impl( } ) ) + +macro_rules! to_homogeneous_impl( + ($t: ident, $t2: ident) => + { + impl ToHomogeneous<$t2> for $t + { + fn to_homogeneous(&self) -> $t2 + { + let mut res: $t2 = One::one(); + + for self.iter().zip(res.mut_iter()).advance |(in, out)| + { *out = copy *in } + + res + } + } + } +) + +macro_rules! from_homogeneous_impl( + ($t: ident, $t2: ident, $dim2: expr) => + { + impl + One + Zero> FromHomogeneous<$t2> for $t + { + fn from_homogeneous(v: &$t2) -> $t + { + let mut res: $t = Zero::zero(); + + for v.iter().zip(res.mut_iter()).advance |(in, out)| + { *out = copy *in } + + res.scalar_div(&v.at[$dim2 - 1]); + + res + } + } + } +)