TooN 2.1
|
00001 // -*- c++ -*- 00002 00003 // Copyright (C) 2011 Tom Drummond (twd20@cam.ac.uk) 00004 // 00005 // This file is part of the TooN Library. This library is free 00006 // software; you can redistribute it and/or modify it under the 00007 // terms of the GNU General Public License as published by the 00008 // Free Software Foundation; either version 2, or (at your option) 00009 // any later version. 00010 00011 // This library is distributed in the hope that it will be useful, 00012 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 // GNU General Public License for more details. 00015 00016 // You should have received a copy of the GNU General Public License along 00017 // with this library; see the file COPYING. If not, write to the Free 00018 // Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, 00019 // USA. 00020 00021 // As a special exception, you may use this file as part of a free software 00022 // library without restriction. Specifically, if other files instantiate 00023 // templates or use macros or inline functions from this file, or you compile 00024 // this file and link it with other files to produce an executable, this 00025 // file does not by itself cause the resulting executable to be covered by 00026 // the GNU General Public License. This exception does not however 00027 // invalidate any other reasons why the executable file might be covered by 00028 // the GNU General Public License. 00029 00030 #ifndef TOON_INCLUDE_SIM3_H 00031 #define TOON_INCLUDE_SIM3_H 00032 00033 #include <TooN/se3.h> 00034 00035 namespace TooN { 00036 00037 00038 /// Represent a three-dimensional similarity transformation (a rotation, a scale factor and a translation). 00039 /// This can be represented by a \f$3\times\f$4 matrix operating on a homogeneous co-ordinate, 00040 /// so that a vector \f$\underline{x}\f$ is transformed to a new location \f$\underline{x}'\f$ 00041 /// by 00042 /// \f[\begin{aligned}\underline{x}' &= E\times\underline{x}\\ \begin{bmatrix}x'\\y'\\z'\end{bmatrix} &= \begin{pmatrix}s r_{11} & s r_{12} & s r_{13} & t_1\\s r_{21} & s r_{22} & s r_{23} & t_2\\s r_{31} & s r_{32} & s r_{33} & t_3\end{pmatrix}\begin{bmatrix}x\\y\\z\\1\end{bmatrix}\end{aligned}\f] 00043 /// 00044 /// This transformation is a member of the Lie group SIM3. These can be parameterised with 00045 /// seven numbers (in the space of the Lie Algebra). In this class, the first three parameters are a 00046 /// translation vector while the second three are a rotation vector, whose direction is the axis of rotation 00047 /// and length the amount of rotation (in radians), as for SO3. The seventh parameter is the log of the scale of the transformation. 00048 /// @ingroup gTransforms 00049 template <typename Precision = DefaultPrecision> 00050 class SIM3 { 00051 public: 00052 /// Default constructor. Initialises the the rotation to zero (the identity), the scale to one and the translation to zero 00053 inline SIM3() : my_scale(1) {} 00054 00055 template <int S, typename P, typename A> 00056 SIM3(const SO3<Precision> & R, const Vector<S, P, A>& T, const Precision & s) : my_se3(R,T), my_scale(s) {} 00057 template <int S, typename P, typename A> 00058 SIM3(const Vector<S, P, A> & v) { *this = exp(v); } 00059 00060 /// Returns the rotation part of the transformation as a SO3 00061 inline SO3<Precision>& get_rotation(){return my_se3.get_rotation();} 00062 /// @overload 00063 inline const SO3<Precision>& get_rotation() const {return my_se3.get_rotation();} 00064 00065 /// Returns the translation part of the transformation as a Vector 00066 inline Vector<3, Precision>& get_translation() {return my_se3.get_translation();} 00067 /// @overload 00068 inline const Vector<3, Precision>& get_translation() const {return my_se3.get_translation();} 00069 00070 /// Returns the scale factor 00071 inline Precision & get_scale() { return my_scale; } 00072 /// @overload 00073 inline const Precision & get_scale() const { return my_scale; } 00074 00075 /// Exponentiate a Vector in the Lie Algebra to generate a new SIM3. 00076 /// See the Detailed Description for details of this vector. 00077 /// @param vect The Vector to exponentiate 00078 template <int S, typename P, typename A> 00079 static inline SIM3 exp(const Vector<S, P, A>& vect); 00080 00081 /// Take the logarithm of the matrix, generating the corresponding vector in the Lie Algebra. 00082 /// See the Detailed Description for details of this vector. 00083 static inline Vector<7, Precision> ln(const SIM3& se3); 00084 /// @overload 00085 inline Vector<7, Precision> ln() const { return SIM3::ln(*this); } 00086 00087 inline SIM3 inverse() const { 00088 const SO3<Precision> rinv = get_rotation().inverse(); 00089 const Precision inv_scale = 1/my_scale; 00090 return SIM3(rinv, -(inv_scale*(rinv*get_translation())), inv_scale); 00091 } 00092 00093 /// Right-multiply by another SIM3 (concatenate the two transformations) 00094 /// @param rhs The multipier 00095 inline SIM3& operator *=(const SIM3& rhs) { 00096 get_translation() += get_rotation() * (get_scale() * rhs.get_translation()); 00097 get_rotation() *= rhs.get_rotation(); 00098 get_scale() *= rhs.get_scale(); 00099 return *this; 00100 } 00101 00102 /// Right-multiply by another SIM3 (concatenate the two transformations) 00103 /// @param rhs The multipier 00104 template<typename P> 00105 inline SIM3<typename Internal::MultiplyType<Precision, P>::type> operator *(const SIM3<P>& rhs) const { 00106 return SIM3<typename Internal::MultiplyType<Precision, P>::type>(get_rotation()*rhs.get_rotation(), get_translation() + get_rotation()*(get_scale()*rhs.get_translation()), get_scale()*rhs.get_scale()); 00107 } 00108 00109 inline SIM3& left_multiply_by(const SIM3& left) { 00110 get_translation() = left.get_translation() + left.get_rotation() * (left.get_scale() * get_translation()); 00111 get_rotation() = left.get_rotation() * get_rotation(); 00112 get_scale() = left.get_scale() * get_scale(); 00113 return *this; 00114 } 00115 00116 static inline Matrix<4,4,Precision> generator(int i){ 00117 Matrix<4,4,Precision> result(Zeros); 00118 if(i < 3){ 00119 result(i,3)=1; 00120 return result; 00121 } 00122 if(i < 6){ 00123 result[(i+1)%3][(i+2)%3] = -1; 00124 result[(i+2)%3][(i+1)%3] = 1; 00125 return result; 00126 } 00127 result(0,0) = 1; 00128 result(1,1) = 1; 00129 result(2,2) = 1; 00130 return result; 00131 } 00132 00133 /// Returns the i-th generator times pos 00134 template<typename Base> 00135 inline static Vector<4,Precision> generator_field(int i, const Vector<4, Precision, Base>& pos) 00136 { 00137 Vector<4, Precision> result(Zeros); 00138 if(i < 3){ 00139 result[i]=pos[3]; 00140 return result; 00141 } 00142 if(i < 6){ 00143 result[(i+1)%3] = - pos[(i+2)%3]; 00144 result[(i+2)%3] = pos[(i+1)%3]; 00145 return result; 00146 } 00147 result.template slice<0,3>() = pos.template slice<0,3>(); 00148 return result; 00149 } 00150 00151 /// Transfer a matrix in the Lie Algebra from one 00152 /// co-ordinate frame to another. This is the operation such that for a matrix 00153 /// \f$ B \f$, 00154 /// \f$ e^{\text{Adj}(v)} = Be^{v}B^{-1} \f$ 00155 /// @param M The Matrix to transfer 00156 template<int S, typename P2, typename Accessor> 00157 inline Vector<7, Precision> adjoint(const Vector<S,P2, Accessor>& vect)const; 00158 00159 /// Transfer covectors between frames (using the transpose of the inverse of the adjoint) 00160 /// so that trinvadjoint(vect1) * adjoint(vect2) = vect1 * vect2 00161 template<int S, typename P2, typename Accessor> 00162 inline Vector<7, Precision> trinvadjoint(const Vector<S,P2,Accessor>& vect)const; 00163 00164 ///@overload 00165 template <int R, int C, typename P2, typename Accessor> 00166 inline Matrix<7,7,Precision> adjoint(const Matrix<R,C,P2,Accessor>& M)const; 00167 00168 ///@overload 00169 template <int R, int C, typename P2, typename Accessor> 00170 inline Matrix<7,7,Precision> trinvadjoint(const Matrix<R,C,P2,Accessor>& M)const; 00171 00172 private: 00173 SE3<Precision> my_se3; 00174 Precision my_scale; 00175 }; 00176 00177 // transfers a vector in the Lie algebra 00178 // from one coord frame to another 00179 // so that exp(adjoint(vect)) = (*this) * exp(vect) * (this->inverse()) 00180 template<typename Precision> 00181 template<int S, typename P2, typename Accessor> 00182 inline Vector<7, Precision> SIM3<Precision>::adjoint(const Vector<S,P2, Accessor>& vect) const{ 00183 SizeMismatch<7,S>::test(7, vect.size()); 00184 Vector<7, Precision> result; 00185 result.template slice<3,3>() = get_rotation() * vect.template slice<3,3>(); 00186 result.template slice<0,3>() = get_rotation() * vect.template slice<0,3>(); 00187 result.template slice<0,3>() += get_translation() ^ result.template slice<3,3>(); 00188 return result; 00189 } 00190 00191 // tansfers covectors between frames 00192 // (using the transpose of the inverse of the adjoint) 00193 // so that trinvadjoint(vect1) * adjoint(vect2) = vect1 * vect2 00194 template<typename Precision> 00195 template<int S, typename P2, typename Accessor> 00196 inline Vector<7, Precision> SIM3<Precision>::trinvadjoint(const Vector<S,P2, Accessor>& vect) const{ 00197 SizeMismatch<7,S>::test(7, vect.size()); 00198 Vector<7, Precision> result; 00199 result.template slice<3,3>() = get_rotation() * vect.template slice<3,3>(); 00200 result.template slice<0,3>() = get_rotation() * vect.template slice<0,3>(); 00201 result.template slice<3,3>() += get_translation() ^ result.template slice<0,3>(); 00202 return result; 00203 } 00204 00205 template<typename Precision> 00206 template<int R, int C, typename P2, typename Accessor> 00207 inline Matrix<7,7,Precision> SIM3<Precision>::adjoint(const Matrix<R,C,P2,Accessor>& M)const{ 00208 SizeMismatch<7,R>::test(7, M.num_cols()); 00209 SizeMismatch<7,C>::test(7, M.num_rows()); 00210 00211 Matrix<7,7,Precision> result; 00212 for(int i=0; i<7; i++){ 00213 result.T()[i] = adjoint(M.T()[i]); 00214 } 00215 for(int i=0; i<7; i++){ 00216 result[i] = adjoint(result[i]); 00217 } 00218 return result; 00219 } 00220 00221 template<typename Precision> 00222 template<int R, int C, typename P2, typename Accessor> 00223 inline Matrix<7,7,Precision> SIM3<Precision>::trinvadjoint(const Matrix<R,C,P2,Accessor>& M)const{ 00224 SizeMismatch<7,R>::test(7, M.num_cols()); 00225 SizeMismatch<7,C>::test(7, M.num_rows()); 00226 00227 Matrix<7,7,Precision> result; 00228 for(int i=0; i<7; i++){ 00229 result.T()[i] = trinvadjoint(M.T()[i]); 00230 } 00231 for(int i=0; i<7; i++){ 00232 result[i] = trinvadjoint(result[i]); 00233 } 00234 return result; 00235 } 00236 00237 /// Write an SIM3 to a stream 00238 /// @relates SIM3 00239 template <typename Precision> 00240 inline std::ostream& operator <<(std::ostream& os, const SIM3<Precision>& rhs){ 00241 std::streamsize fw = os.width(); 00242 for(int i=0; i<3; i++){ 00243 os.width(fw); 00244 os << rhs.get_rotation().get_matrix()[i] * rhs.get_scale(); 00245 os.width(fw); 00246 os << rhs.get_translation()[i] << '\n'; 00247 } 00248 return os; 00249 } 00250 00251 00252 /// Reads an SIM3 from a stream 00253 /// @relates SIM3 00254 template <typename Precision> 00255 inline std::istream& operator>>(std::istream& is, SIM3<Precision>& rhs){ 00256 for(int i=0; i<3; i++){ 00257 is >> rhs.get_rotation().my_matrix[i].ref() >> rhs.get_translation()[i]; 00258 } 00259 rhs.get_scale() = (norm(rhs.get_rotation().my_matrix[0]) + norm(rhs.get_rotation().my_matrix[1]) + norm(rhs.get_rotation().my_matrix[2]))/3; 00260 rhs.get_rotation().coerce(); 00261 return is; 00262 } 00263 00264 ////////////////// 00265 // operator * // 00266 // SIM3 * Vector // 00267 ////////////////// 00268 00269 namespace Internal { 00270 template<int S, typename PV, typename A, typename P> 00271 struct SIM3VMult; 00272 } 00273 00274 template<int S, typename PV, typename A, typename P> 00275 struct Operator<Internal::SIM3VMult<S,PV,A,P> > { 00276 const SIM3<P> & lhs; 00277 const Vector<S,PV,A> & rhs; 00278 00279 Operator(const SIM3<P> & l, const Vector<S,PV,A> & r ) : lhs(l), rhs(r) {} 00280 00281 template <int S0, typename P0, typename A0> 00282 void eval(Vector<S0, P0, A0> & res ) const { 00283 SizeMismatch<4,S>::test(4, rhs.size()); 00284 res.template slice<0,3>()=lhs.get_rotation() * (lhs.get_scale() * rhs.template slice<0,3>()); 00285 res.template slice<0,3>()+=TooN::operator*(lhs.get_translation(),rhs[3]); 00286 res[3] = rhs[3]; 00287 } 00288 int size() const { return 4; } 00289 }; 00290 00291 /// Right-multiply by a Vector 00292 /// @relates SIM3 00293 template<int S, typename PV, typename A, typename P> inline 00294 Vector<4, typename Internal::MultiplyType<P,PV>::type> operator*(const SIM3<P> & lhs, const Vector<S,PV,A>& rhs){ 00295 return Vector<4, typename Internal::MultiplyType<P,PV>::type>(Operator<Internal::SIM3VMult<S,PV,A,P> >(lhs,rhs)); 00296 } 00297 00298 /// Right-multiply by a Vector 00299 /// @relates SIM3 00300 template <typename PV, typename A, typename P> inline 00301 Vector<3, typename Internal::MultiplyType<P,PV>::type> operator*(const SIM3<P>& lhs, const Vector<3,PV,A>& rhs){ 00302 return lhs.get_translation() + lhs.get_rotation() * (lhs.get_scale() * rhs); 00303 } 00304 00305 ////////////////// 00306 // operator * // 00307 // Vector * SIM3 // 00308 ////////////////// 00309 00310 namespace Internal { 00311 template<int S, typename PV, typename A, typename P> 00312 struct VSIM3Mult; 00313 } 00314 00315 template<int S, typename PV, typename A, typename P> 00316 struct Operator<Internal::VSIM3Mult<S,PV,A,P> > { 00317 const Vector<S,PV,A> & lhs; 00318 const SIM3<P> & rhs; 00319 00320 Operator( const Vector<S,PV,A> & l, const SIM3<P> & r ) : lhs(l), rhs(r) {} 00321 00322 template <int S0, typename P0, typename A0> 00323 void eval(Vector<S0, P0, A0> & res ) const { 00324 SizeMismatch<4,S>::test(4, lhs.size()); 00325 res.template slice<0,3>()= rhs.get_scale() * lhs.template slice<0,3>() * rhs.get_rotation(); 00326 res[3] = lhs[3]; 00327 res[3] += lhs.template slice<0,3>() * rhs.get_translation(); 00328 } 00329 int size() const { return 4; } 00330 }; 00331 00332 /// Left-multiply by a Vector 00333 /// @relates SIM3 00334 template<int S, typename PV, typename A, typename P> inline 00335 Vector<4, typename Internal::MultiplyType<P,PV>::type> operator*( const Vector<S,PV,A>& lhs, const SIM3<P> & rhs){ 00336 return Vector<4, typename Internal::MultiplyType<P,PV>::type>(Operator<Internal::VSIM3Mult<S,PV,A,P> >(lhs,rhs)); 00337 } 00338 00339 ////////////////// 00340 // operator * // 00341 // SIM3 * Matrix // 00342 ////////////////// 00343 00344 namespace Internal { 00345 template <int R, int C, typename PM, typename A, typename P> 00346 struct SIM3MMult; 00347 } 00348 00349 template<int R, int Cols, typename PM, typename A, typename P> 00350 struct Operator<Internal::SIM3MMult<R, Cols, PM, A, P> > { 00351 const SIM3<P> & lhs; 00352 const Matrix<R,Cols,PM,A> & rhs; 00353 00354 Operator(const SIM3<P> & l, const Matrix<R,Cols,PM,A> & r ) : lhs(l), rhs(r) {} 00355 00356 template <int R0, int C0, typename P0, typename A0> 00357 void eval(Matrix<R0, C0, P0, A0> & res ) const { 00358 SizeMismatch<4,R>::test(4, rhs.num_rows()); 00359 for(int i=0; i<rhs.num_cols(); ++i) 00360 res.T()[i] = lhs * rhs.T()[i]; 00361 } 00362 int num_cols() const { return rhs.num_cols(); } 00363 int num_rows() const { return 4; } 00364 }; 00365 00366 /// Right-multiply by a Matrix 00367 /// @relates SIM3 00368 template <int R, int Cols, typename PM, typename A, typename P> inline 00369 Matrix<4,Cols, typename Internal::MultiplyType<P,PM>::type> operator*(const SIM3<P> & lhs, const Matrix<R,Cols,PM, A>& rhs){ 00370 return Matrix<4,Cols,typename Internal::MultiplyType<P,PM>::type>(Operator<Internal::SIM3MMult<R, Cols, PM, A, P> >(lhs,rhs)); 00371 } 00372 00373 ////////////////// 00374 // operator * // 00375 // Matrix * SIM3 // 00376 ////////////////// 00377 00378 namespace Internal { 00379 template <int Rows, int C, typename PM, typename A, typename P> 00380 struct MSIM3Mult; 00381 } 00382 00383 template<int Rows, int C, typename PM, typename A, typename P> 00384 struct Operator<Internal::MSIM3Mult<Rows, C, PM, A, P> > { 00385 const Matrix<Rows,C,PM,A> & lhs; 00386 const SIM3<P> & rhs; 00387 00388 Operator( const Matrix<Rows,C,PM,A> & l, const SIM3<P> & r ) : lhs(l), rhs(r) {} 00389 00390 template <int R0, int C0, typename P0, typename A0> 00391 void eval(Matrix<R0, C0, P0, A0> & res ) const { 00392 SizeMismatch<4, C>::test(4, lhs.num_cols()); 00393 for(int i=0; i<lhs.num_rows(); ++i) 00394 res[i] = lhs[i] * rhs; 00395 } 00396 int num_cols() const { return 4; } 00397 int num_rows() const { return lhs.num_rows(); } 00398 }; 00399 00400 /// Left-multiply by a Matrix 00401 /// @relates SIM3 00402 template <int Rows, int C, typename PM, typename A, typename P> inline 00403 Matrix<Rows,4, typename Internal::MultiplyType<PM,P>::type> operator*(const Matrix<Rows,C,PM, A>& lhs, const SIM3<P> & rhs ){ 00404 return Matrix<Rows,4,typename Internal::MultiplyType<PM,P>::type>(Operator<Internal::MSIM3Mult<Rows, C, PM, A, P> >(lhs,rhs)); 00405 } 00406 00407 namespace Internal { 00408 00409 /// internal function that calculates the coefficients for the Rodrigues formula for SIM3 translation 00410 template <typename Precision> 00411 inline Vector<3, Precision> compute_rodrigues_coefficients_sim3( const Precision & s, const Precision & t ){ 00412 using std::exp; 00413 00414 Vector<3, Precision> coeff; 00415 const Precision es = exp(s); 00416 00417 // 4 cases for s -> 0 and/or theta -> 0 00418 // the Taylor expansions were calculated with Maple 12 and truncated at the 3rd power, 00419 // such that eps^3 < 1e-18 which results in approximately 1 + eps^3 = 1 00420 static const Precision eps = 1e-6; 00421 00422 if(fabs(s) < eps && fabs(t) < eps){ 00423 coeff[0] = 1 + s/2 + s*s/6; 00424 coeff[1] = 1/2 + s/3 - t*t/24 + s*s/8; 00425 coeff[2] = 1/6 + s/8 - t*t/120 + s*s/20; 00426 } else if(fabs(s) < eps) { 00427 coeff[0] = 1 + s/2 + s*s/6; 00428 coeff[1] = (1-cos(t))/(t*t) + (sin(t)-cos(t)*t)*s/(t*t*t)+(2*sin(t)*t-t*t*cos(t)-2+2*cos(t))*s*s/(2*t*t*t*t); 00429 coeff[2] = (t-sin(t))/(t*t*t) - (-t*t-2+2*cos(t)+2*sin(t)*t)*s/(2*t*t*t*t) - (-t*t*t+6*cos(t)*t+3*sin(t)*t*t-6*sin(t))*s*s/(6*t*t*t*t*t); 00430 } else if(fabs(t) < eps) { 00431 coeff[0] = (es - 1)/s; 00432 coeff[1] = (s*es+1-es)/(s*s) - (6*s*es+6-6*es+es*s*s*s-3*es*s*s)*t*t/(6*s*s*s*s); 00433 coeff[2] = (es*s*s-2*s*es+2*es-2)/(2*s*s*s) - (es*s*s*s*s-4*es*s*s*s+12*es*s*s-24*s*es+24*es-24)*t*t/(24*s*s*s*s*s); 00434 } else { 00435 const Precision a = es * sin(t); 00436 const Precision b = es * cos(t); 00437 const Precision inv_s_theta = 1/(s*s + t*t); 00438 00439 coeff[0] = (es - 1)/s; 00440 coeff[1] = (a*s + (1-b)*t) * inv_s_theta / t; 00441 coeff[2] = (coeff[0] - ((b-1)*s + a*t) * inv_s_theta) / (t*t); 00442 } 00443 00444 return coeff; 00445 } 00446 00447 } 00448 00449 template <typename Precision> 00450 template <int S, typename P, typename VA> 00451 inline SIM3<Precision> SIM3<Precision>::exp(const Vector<S, P, VA>& mu){ 00452 SizeMismatch<7,S>::test(7, mu.size()); 00453 using std::exp; 00454 00455 SIM3<Precision> result; 00456 00457 // scale 00458 result.get_scale() = exp(mu[6]); 00459 00460 // rotation 00461 const Vector<3,Precision> w = mu.template slice<3,3>(); 00462 const Precision t = norm(w); 00463 result.get_rotation() = SO3<>::exp(w); 00464 00465 // translation 00466 const Vector<3, Precision> coeff = Internal::compute_rodrigues_coefficients_sim3(mu[6],t); 00467 const Vector<3,Precision> cross = w ^ mu.template slice<0,3>(); 00468 result.get_translation() = coeff[0] * mu.template slice<0,3>() + TooN::operator*(coeff[1], cross) + TooN::operator*(coeff[2], (w ^ cross)); 00469 00470 return result; 00471 } 00472 00473 template <typename Precision> 00474 inline Vector<7, Precision> SIM3<Precision>::ln(const SIM3<Precision>& sim3) { 00475 using std::sqrt; 00476 using std::log; 00477 00478 Vector<7, Precision> result; 00479 00480 // rotation 00481 result.template slice<3,3>() = sim3.get_rotation().ln(); 00482 const Precision theta = norm(result.template slice<3,3>()); 00483 00484 // scale 00485 const Precision es = sim3.get_scale(); 00486 const Precision s = log(sim3.get_scale()); 00487 result[6] = s; 00488 00489 // Translation 00490 const Vector<3, Precision> coeff = Internal::compute_rodrigues_coefficients_sim3(s, theta); 00491 const Matrix<3,3, Precision> cross = cross_product_matrix(result.template slice<3,3>()); 00492 const Matrix<3,3, Precision> W = Identity * coeff[0] + cross * coeff[1] + cross * cross * coeff[2]; 00493 00494 result.template slice<0,3>() = gaussian_elimination(W, sim3.get_translation()); 00495 00496 return result; 00497 } 00498 00499 #if 0 00500 template <typename Precision> 00501 inline SE3<Precision> operator*(const SO3<Precision>& lhs, const SE3<Precision>& rhs){ 00502 return SE3<Precision>(lhs*rhs.get_rotation(),lhs*rhs.get_translation()); 00503 } 00504 #endif 00505 00506 } 00507 00508 #endif