00001 /************************************************************************/ 00002 /* This file is part of openMask(c) INRIA, CNRS, Universite de Rennes 1 */ 00003 /* 1993-2002, thereinafter the Software */ 00004 /* */ 00005 /* The Software has been developped within the Siames Project. */ 00006 /* INRIA, the University of Rennes 1 and CNRS jointly hold intellectual */ 00007 /* property rights */ 00008 /* */ 00009 /* The Software has been registered with the Agence pour la Protection */ 00010 /* des Programmes (APP) under registration number */ 00011 /* IDDN.FR.001.510008.00.S.P.2001.000.41200 */ 00012 /* */ 00013 /* This file may be distributed under the terms of the Q Public License */ 00014 /* version 1.0 as defined by Trolltech AS of Norway and appearing in */ 00015 /* the file LICENSE.QPL included in the packaging of this file. */ 00016 /* */ 00017 /* Licensees holding valid specific licenses issued by INRIA, CNRS or */ 00018 /* Universite Rennes 1 for the software may use this file in */ 00019 /* acordance with that specific license */ 00020 /************************************************************************/ 00021 00022 #include "OMKJointExtension.h" 00023 #include "OMKJoint.h" 00024 #include "OMKExtensibleSimulatedObject.h" 00025 #include "OMKInteractorExtension.h" 00026 #include "OMKParametersAccessor.inl" 00027 #include "OMKInputNT.h" 00028 #include "OMKSessionPrm.h" 00029 #include "OMKTransformType.h" 00030 #include "Wm4Plane3.h" 00031 #include "Wm4IntrLine3Plane3.h" 00032 #include "Wm4IntrRay3Sphere3.h" 00033 00034 namespace OMK 00035 { 00036 namespace Iii 00037 { 00038 //================================================================= 00039 // Joint 00040 //================================================================= 00041 00042 REGISTER_JOINT_FACTORY( Joint, "Lock" ) ; 00043 00044 //----------------------------------------------------------------- 00045 Joint::Joint( JointExtension* owner, const ConfigurationParameterDescriptor * node ) 00046 : _jointOffset( OMK::Type::Transform::sk_identity ), 00047 _jointOffsetInverse( OMK::Type::Transform::sk_identity ), 00048 _objectOffset( OMK::Type::Transform::sk_identity ), 00049 _objectOffsetInverse( OMK::Type::Transform::sk_identity ), 00050 _owner( owner ), 00051 _objectPosition( owner->_objectPosition ), 00052 _referencePosition( &owner->_referencePosition ), 00053 _jointPosition( &owner->_jointPosition ) 00054 { 00055 } 00056 //----------------------------------------------------------------- 00057 Joint::~Joint() 00058 { 00059 } 00060 //----------------------------------------------------------------- 00061 void Joint::establishLink() 00062 { 00063 // Must calculate the new offset between the reference position and the joint position 00064 // It is the current offset 00065 _jointOffset = product( _referencePosition->get().inverse(), _jointPosition->get() ) ; 00066 _jointOffset.updateFlags() ; 00067 00068 // Must calculate the new offset between the joint position and the object position 00069 // It is the current offset 00070 _objectOffset = product( _jointPosition->get().inverse(), _objectPosition->get() ) ; 00071 _objectOffset.updateFlags() ; 00072 00073 // Calculates the inverse transforms for optimisation 00074 _jointOffsetInverse = _jointOffset.inverse() ; 00075 _jointOffsetInverse.updateFlags() ; 00076 00077 _objectOffsetInverse = _objectOffset.inverse() ; 00078 _objectOffsetInverse.updateFlags() ; 00079 } 00080 00081 //----------------------------------------------------------------- 00082 void Joint::currentJointPosition() 00083 { 00084 // calculates the new joint position 00085 _jointPosition->set( product( _referencePosition->get(), _jointOffset ) ) ; 00086 } 00087 //----------------------------------------------------------------- 00088 bool Joint::retroPropagation() 00089 { 00090 // Calculates the optimal position of the reference position to retro-propagates 00091 _referencePosition->set( product( product( _objectPosition->get(), _objectOffsetInverse ), _jointOffsetInverse ) ) ; 00092 return true ; 00093 } 00094 //----------------------------------------------------------------- 00095 void Joint::currentObjectPosition() 00096 { 00097 // calculates the new object position 00098 _objectPosition->set( product( _jointPosition->get(), _objectOffset ) ) ; 00099 } 00100 //----------------------------------------------------------------- 00101 00102 //================================================================= 00103 // BallJoint 00104 //================================================================= 00105 REGISTER_JOINT_FACTORY( BallJoint, "Ball" ) ; 00106 00107 //----------------------------------------------------------------- 00108 BallJoint::BallJoint( JointExtension* owner, const ConfigurationParameterDescriptor * node ) 00109 : Joint( owner, node ), 00110 _newObjectPosition(), 00111 _alreadyKnow( false ), 00112 _radius( 0.0f ) 00113 { 00114 } 00115 //----------------------------------------------------------------- 00116 BallJoint::~BallJoint() 00117 { 00118 } 00119 //----------------------------------------------------------------- 00120 void BallJoint::establishLink() 00121 { 00122 Joint::establishLink() ; 00123 _radius = _objectOffset.getTranslate().Length() ; 00124 00125 // stores the reference offset 00126 _refObjectOffset = _objectOffset ; 00127 _newObjectPosition = _objectPosition->get() ; 00128 _reset = true ; 00129 } 00130 //----------------------------------------------------------------- 00131 bool BallJoint::retroPropagation() 00132 { 00133 if( _reset ) 00134 { 00135 _previousPosition = _objectPosition->get().getTranslate() ; 00136 _reset = false ; 00137 } 00138 // Get the joint axis and perpendicular plane 00139 Wm4::Vector3f axis( _refObjectOffset.getTranslate() ) ; 00140 axis.Normalize() ; 00141 00142 // the reference position according to the position of the joint and the reference offset 00143 OMK::Type::Transform refObjectPosition( product( _jointPosition->get(), _refObjectOffset ) ) ; 00144 00145 // The reference orientation 00146 Wm4::Matrix3f refOrientation = _jointPosition->get().getRotate() ; 00147 // get the new orientation 00148 Wm4::Matrix3f newOrientation = _objectPosition->get().getRotate() * ( refObjectPosition.getRotate().Transpose() * refOrientation ) ; 00149 00150 // Only the translation 00151 bool isConstraint = _objectPosition->get().getTranslate() != refObjectPosition.getTranslate() ; 00152 00153 // The new object position 00154 // = the position of the joint 00155 // + the position along the axis 00156 _newObjectPosition.setTranslate( _jointPosition->get().getTranslate() + ( newOrientation * axis ) * _radius ) ; 00157 _newObjectPosition.setRotate( newOrientation ) ; 00158 _alreadyKnow = true ; 00159 // update the current object offset 00160 _objectOffset = product( _jointPosition->get().inverse(), _newObjectPosition ) ; 00161 _objectOffset.updateFlags() ; 00162 _objectOffsetInverse = _objectOffset.inverse() ; 00163 _objectOffsetInverse.updateFlags() ; 00164 00165 if( isConstraint ) 00166 { 00167 // Calculates the optimal position of the reference position to retro-propagates 00168 OMK::Type::Transform newJointPosition( _jointPosition->get().getTranslate() 00169 + _objectPosition->get().getTranslate() 00170 - _previousPosition, 00171 _jointPosition->get().getRotate() ) ; 00172 _referencePosition->set( product( newJointPosition, _jointOffsetInverse ) ) ; 00173 _reset = true ; 00174 } 00175 00176 return isConstraint ; 00177 } 00178 //----------------------------------------------------------------- 00179 void BallJoint::currentObjectPosition() 00180 { 00181 if( _alreadyKnow ) 00182 { 00183 _alreadyKnow = false ; 00184 _objectPosition->set( _newObjectPosition ) ; 00185 } 00186 else 00187 { 00188 // calculates the new object position 00189 _objectPosition->set( product( _jointPosition->get(), _objectOffset ) ) ; 00190 } 00191 } 00192 00193 00194 //================================================================= 00195 // HingeSlideJoint 00196 //================================================================= 00197 REGISTER_JOINT_FACTORY( HingeSlideJoint, "Hinge" ) ; 00198 00199 //----------------------------------------------------------------- 00200 HingeSlideJoint::HingeSlideJoint( JointExtension* owner, const ConfigurationParameterDescriptor * node ) 00201 : Joint( owner, node ), 00202 _newObjectPosition(), 00203 _refObjectOffset(), 00204 _alreadyKnow( false ), 00205 _radius( 0.0f ), 00206 _refSlide( 0.0f ), 00207 _minSlide( 0.0f ), 00208 _maxSlide( 0.0f ), 00209 _minSlideEnable( false ), 00210 _maxSlideEnable( false ), 00211 _minAngle( 0.0f ), 00212 _maxAngle( 0.0f ), 00213 _angleEnable( false ), 00214 _rotateOnly( false ), 00215 _ignoreTranslate( false ) 00216 00217 { 00218 _minSlideEnable = ParametersAccessor::get( node, "MinSlide", _minSlide ) ; 00219 _maxSlideEnable = ParametersAccessor::get( node, "MaxSlide", _maxSlide ) ; 00220 _angleEnable = ParametersAccessor::get( node, "MinAngle", _minAngle ) 00221 && ParametersAccessor::get( node, "MaxAngle", _maxAngle ) ; 00222 ParametersAccessor::get( node, "RotateOnly", _rotateOnly ) ; 00223 } 00224 //----------------------------------------------------------------- 00225 HingeSlideJoint::~HingeSlideJoint() 00226 { 00227 } 00228 //----------------------------------------------------------------- 00229 void HingeSlideJoint::establishLink() 00230 { 00231 Joint::establishLink() ; 00232 // Get the joint plane 00233 Wm4::Vector3f axis( _jointPosition->get().getUp() ) ; 00234 axis.Normalize() ; 00235 Wm4::Plane3f plane( axis, _jointPosition->get().getTranslate() ) ; 00236 // The reference slide position 00237 // projection of the current position over the plane perpendicular to the joint axis 00238 Wm4::IntrLine3Plane3f objectOnPlane( Wm4::Line3f( _objectPosition->get().getTranslate(), axis ), plane ) ; 00239 objectOnPlane.Find() ; 00240 // to get the reference slide position 00241 _refSlide = objectOnPlane.GetLineT() ; 00242 // to get the radius on the plane 00243 Wm4::Vector3f projection( _objectPosition->get().getTranslate() + _refSlide * axis ) ; 00244 Wm4::Vector3f radiusVector( projection - _jointPosition->get().getTranslate() ) ; 00245 _radius = radiusVector.Length() ; 00246 _ignoreTranslate = ( _radius < Wm4::Math<float>::ZERO_TOLERANCE ) || _rotateOnly ; 00247 00248 // stores the reference offset 00249 _refObjectOffset = _objectOffset ; 00250 } 00251 //----------------------------------------------------------------- 00252 bool HingeSlideJoint::retroPropagation() 00253 { 00254 // Get the joint axis and perpendicular plane 00255 Wm4::Vector3f axis( _jointPosition->get().getUp() ) ; 00256 axis.Normalize() ; 00257 Wm4::Plane3f plane( axis, _jointPosition->get().getTranslate() ) ; 00258 // the reference position according to the position of the joint and the reference offset 00259 OMK::Type::Transform refObjectPosition( product( _jointPosition->get(), _refObjectOffset ) ) ; 00260 float currentSlide = 0.0f ; 00261 Wm4::Vector3f refOrientation ; 00262 Wm4::Vector3f newOrientation ; 00263 00264 if( _ignoreTranslate ) 00265 { // The axis is on the object position, only rotate 00266 // get a reference position over the joint plane 00267 refOrientation = _jointPosition->get().getRight() ; 00268 // get the new orientation 00269 newOrientation = _objectPosition->get().getRotate() * ( refObjectPosition.getRotate().Transpose() * _jointPosition->get().getRight() ) ; 00270 00271 // the new slide offset 00272 currentSlide = axis.Dot( _jointPosition->get().getTranslate() - _objectPosition->get().getTranslate() ) - _refSlide ; 00273 00274 } 00275 else 00276 { // The axis is far of the object position, only translate 00277 00278 // projection of the reference position over the joint plane 00279 Wm4::Vector3f refIntersection( refObjectPosition.getTranslate() + _refSlide * axis ) ; 00280 // reference orientation 00281 refOrientation = refIntersection - _jointPosition->get().getTranslate() ; 00282 refOrientation.Normalize() ; 00283 00284 // projection of the current position over the joint plane 00285 Wm4::IntrLine3Plane3f objectOnPlane( Wm4::Line3f( _objectPosition->get().getTranslate(), axis ), plane ) ; 00286 objectOnPlane.Find() ; 00287 // the new slide offset 00288 float currentSlideOverThePlane = objectOnPlane.GetLineT() ; 00289 00290 // The new orientation 00291 Wm4::Vector3f projection( _objectPosition->get().getTranslate() + currentSlideOverThePlane * axis ) ; 00292 newOrientation = projection - _jointPosition->get().getTranslate() ; 00293 newOrientation.Normalize() ; 00294 00295 // the new slide offset 00296 currentSlide = currentSlideOverThePlane - _refSlide ; 00297 } 00298 00299 // Compute the angle between the reference and the new orientation 00300 float angle = Wm4::Math<float>::ACos( newOrientation.Dot( refOrientation ) ) 00301 * ( axis.Dot( newOrientation.Cross( refOrientation ) ) < 0.0f ? 1.0f : -1.0f ) ; 00302 00303 Wm4::Matrix3f overRotationMatrix( Wm4::Matrix3f::IDENTITY ) ; 00304 float overSlide = 0.0f ; 00305 bool isConstraint = computeConstraints( angle, currentSlide, 00306 overRotationMatrix, overSlide, newOrientation, 00307 refOrientation, axis ) ; 00308 00309 // The new object position 00310 // = the position of the joint 00311 // + the position on the plane of the joint 00312 // + the position along the axis 00313 _newObjectPosition.setTranslate( _jointPosition->get().getTranslate() + newOrientation * _radius - ( _refSlide + currentSlide ) * axis ) ; 00314 // the rotation of the object compared to the position of reference 00315 Wm4::Matrix3f rotationMatrix( axis, angle ) ; 00316 _newObjectPosition.setRotate( rotationMatrix * refObjectPosition.getRotate() ) ; 00317 _alreadyKnow = true ; 00318 // update the current object offset 00319 _objectOffset = product( _jointPosition->get().inverse(), _newObjectPosition ) ; 00320 _objectOffset.updateFlags() ; 00321 _objectOffsetInverse = _objectOffset.inverse() ; 00322 _objectOffsetInverse.updateFlags() ; 00323 00324 if( isConstraint ) 00325 { 00326 // Calculates the optimal position of the reference position to retro-propagates 00327 OMK::Type::Transform newJointPosition( _jointPosition->get().getTranslate() - overSlide * axis, overRotationMatrix * _jointPosition->get().getRotate() ) ; 00328 _referencePosition->set( product( newJointPosition, _jointOffsetInverse ) ) ; 00329 } 00330 00331 return isConstraint ; 00332 } 00333 //----------------------------------------------------------------- 00334 bool HingeSlideJoint::computeConstraints( float& angle, float& currentSlide, 00335 Wm4::Matrix3f& overRotationMatrix, float& overSlide, 00336 Wm4::Vector3f& newOrientation, 00337 const Wm4::Vector3f& refOrientation, const Wm4::Vector3f& axis ) 00338 { 00339 bool isConstraint = false ; 00340 overRotationMatrix.MakeIdentity() ; 00341 overSlide = 0.0f ; 00342 // Compute the constraint 00343 if( _angleEnable ) 00344 { 00345 if( angle < _minAngle ) 00346 { 00347 overRotationMatrix.FromAxisAngle( axis, angle - _minAngle ) ; 00348 angle = _minAngle ; 00349 isConstraint = true ; 00350 } 00351 else if( _maxAngle < angle ) 00352 { 00353 overRotationMatrix.FromAxisAngle( axis, angle - _maxAngle ) ; 00354 angle = _maxAngle ; 00355 isConstraint = true ; 00356 } 00357 if( isConstraint ) 00358 { 00359 newOrientation = Wm4::Matrix3f( axis, angle ) * refOrientation ; 00360 } 00361 } 00362 00363 // Compute the constraint 00364 if( _minSlideEnable && ( currentSlide < _minSlide ) ) 00365 { 00366 overSlide = currentSlide - _minSlide ; 00367 currentSlide = _minSlide ; 00368 isConstraint = true ; 00369 } 00370 else if( _maxSlideEnable && ( _maxSlide < currentSlide ) ) 00371 { 00372 overSlide = currentSlide - _maxSlide ; 00373 currentSlide = _maxSlide ; 00374 isConstraint = true ; 00375 } 00376 return isConstraint ; 00377 } 00378 //----------------------------------------------------------------- 00379 void HingeSlideJoint::currentObjectPosition() 00380 { 00381 if( _alreadyKnow ) 00382 { 00383 _alreadyKnow = false ; 00384 _objectPosition->set( _newObjectPosition ) ; 00385 } 00386 else 00387 { 00388 // calculates the new object position 00389 _objectPosition->set( product( _jointPosition->get(), _objectOffset ) ) ; 00390 } 00391 } 00392 00393 00394 00395 //================================================================= 00396 // ScrewJoint 00397 //================================================================= 00398 REGISTER_JOINT_FACTORY( ScrewJoint, "Screw" ) ; 00399 00400 //----------------------------------------------------------------- 00401 ScrewJoint::ScrewJoint( JointExtension* owner, const ConfigurationParameterDescriptor * node ) 00402 : HingeSlideJoint( owner, node ), 00403 _pitch( 0.0f ), 00404 _reverse( false ), 00405 _state( NOSCREW ), 00406 _stateMin( NOSCREW ), 00407 _stateMax( NOSCREW ), 00408 _previousAngle( 0.0f ), 00409 _turns( 0.0f ) 00410 { 00411 ParametersAccessor::get( node, "Pitch", _pitch ) ; 00412 _stateMin = ( 0.0f < _pitch ) ? UNSCREW : SCREW ; 00413 _stateMax = ( 0.0f < _pitch ) ? SCREW : UNSCREW ; 00414 OMASSERTM( _pitch != 0.0f, "The screw pitch cannot be null" ) ; 00415 ParametersAccessor::get( node, "Reverse", _reverse ) ; 00416 _angleEnable = false ; 00417 } 00418 //----------------------------------------------------------------- 00419 ScrewJoint::~ScrewJoint() 00420 { 00421 } 00422 //----------------------------------------------------------------- 00423 void ScrewJoint::establishLink() 00424 { 00425 _turns = 0.0f ; 00426 _previousAngle = 0.0f ; 00427 _state = NOSCREW ; 00428 HingeSlideJoint::establishLink() ; 00429 _previousOrientation = _jointPosition->get().getRight() ; 00430 } 00431 //----------------------------------------------------------------- 00432 bool ScrewJoint::computeConstraints( float& angle, float& currentSlide, 00433 Wm4::Matrix3f& overRotationMatrix, float& overSlide, 00434 Wm4::Vector3f& newOrientation, 00435 const Wm4::Vector3f& refOrientation, const Wm4::Vector3f& axis ) 00436 { 00437 overRotationMatrix.MakeIdentity() ; 00438 overSlide = 0.0f ; 00439 bool isConstraint = false ; 00440 if( _reverse ) 00441 { // angle is a function of the slide 00442 // Compute the constraint 00443 if( _minSlideEnable && ( currentSlide < _minSlide ) ) 00444 { 00445 overSlide = currentSlide - _minSlide ; 00446 currentSlide = _minSlide ; 00447 isConstraint = true ; 00448 } 00449 else if( _maxSlideEnable && ( _maxSlide < currentSlide ) ) 00450 { 00451 overSlide = currentSlide - _maxSlide ; 00452 currentSlide = _maxSlide ; 00453 isConstraint = true ; 00454 } 00455 // The angle is only a function of the slide offset 00456 angle = currentSlide / _pitch * -Wm4::Math<float>::TWO_PI ; 00457 newOrientation = Wm4::Matrix3f( axis, angle ) * refOrientation ; 00458 } 00459 else 00460 { // slide is a function of the angle 00461 // Computes the angle between the previous and the new orientation to determine if it screws or unscrews 00462 float screw = axis.Dot( newOrientation.Cross( _previousOrientation ) ) ; 00463 switch( _state ) 00464 { 00465 case NOSCREW : 00466 if( Wm4::Math<float>::ZERO_TOLERANCE < screw ) _state = SCREW ; 00467 else if( screw < -Wm4::Math<float>::ZERO_TOLERANCE ) _state = UNSCREW ; 00468 // Calculates the slide 00469 currentSlide = ( _turns + angle * -Wm4::Math<float>::INV_TWO_PI ) * _pitch ; 00470 break ; 00471 case SCREW : 00472 case UNSCREW : 00473 if( Wm4::Math<float>::ZERO_TOLERANCE < screw ) 00474 { 00475 _state = SCREW ; 00476 if( _previousAngle < angle ) _turns += 1.0f ; 00477 } 00478 else if( screw < -Wm4::Math<float>::ZERO_TOLERANCE ) 00479 { 00480 _state = UNSCREW ; 00481 if( angle < _previousAngle ) _turns -= 1.0f ; 00482 } 00483 else _state = NOSCREW ; 00484 // Calculates the slide 00485 currentSlide = ( _turns + angle * -Wm4::Math<float>::INV_TWO_PI ) * _pitch ; 00486 // Applies the constraintes 00487 if( _state == _stateMin && _minSlideEnable && ( currentSlide < _minSlide ) ) 00488 { 00489 _state = IS_MIN ; 00490 computeConstraintAngle( angle, _minSlide, newOrientation, refOrientation, axis ) ; 00491 } 00492 else if( _state == _stateMax && _maxSlideEnable && ( _maxSlide < currentSlide ) ) 00493 { 00494 _state = IS_MAX ; 00495 computeConstraintAngle( angle, _maxSlide, newOrientation, refOrientation, axis ) ; 00496 } 00497 break ; 00498 case IS_MIN : 00499 { 00500 if( -Wm4::Math<float>::ZERO_TOLERANCE < screw ) _state = NOSCREW ; 00501 // The angle is only a function of the slide offset 00502 currentSlide = _minSlide ; 00503 overRotationMatrix.FromAxisAngle( axis, angle - computeConstraintAngle( angle, currentSlide, newOrientation, refOrientation, axis ) ) ; 00504 isConstraint = true ; 00505 } 00506 break ; 00507 case IS_MAX : 00508 { 00509 if( screw < Wm4::Math<float>::ZERO_TOLERANCE) _state = NOSCREW ; 00510 // The angle is only a function of the slide offset 00511 currentSlide = _maxSlide ; 00512 overRotationMatrix.FromAxisAngle( axis, angle - computeConstraintAngle( angle, currentSlide, newOrientation, refOrientation, axis ) ) ; 00513 isConstraint = true ; 00514 } 00515 break ; 00516 } 00517 // min and max slide saturation 00518 if( _minSlideEnable && ( currentSlide < _minSlide ) ) 00519 { 00520 currentSlide = _minSlide ; 00521 } 00522 else if( _maxSlideEnable && ( _maxSlide < currentSlide ) ) 00523 { 00524 currentSlide = _maxSlide ; 00525 } 00526 _turns = currentSlide / _pitch + angle * Wm4::Math<float>::INV_TWO_PI ; 00527 // Stores current values which will be the previous ones of the next step 00528 _previousAngle = angle ; 00529 _previousOrientation = newOrientation ; 00530 } 00531 00532 return isConstraint ; 00533 } 00534 float ScrewJoint::computeConstraintAngle( float& angle, const float& currentSlide, 00535 Wm4::Vector3f& newOrientation, 00536 const Wm4::Vector3f& refOrientation, const Wm4::Vector3f& axis ) 00537 { 00538 float constraintAngle = currentSlide / _pitch * -Wm4::Math<float>::TWO_PI ; 00539 if( constraintAngle < 0.0f ) 00540 { 00541 constraintAngle = Wm4::Math<float>::FMod( -constraintAngle + Wm4::Math<float>::TWO_PI + Wm4::Math<float>::PI, Wm4::Math<float>::TWO_PI ) - Wm4::Math<float>::PI ; 00542 } 00543 else 00544 { 00545 constraintAngle = Wm4::Math<float>::FMod( constraintAngle + Wm4::Math<float>::PI, Wm4::Math<float>::TWO_PI ) - Wm4::Math<float>::PI ; 00546 } 00547 newOrientation = Wm4::Matrix3f( axis, constraintAngle ) * refOrientation ; 00548 angle = constraintAngle ; 00549 return constraintAngle ; 00550 } 00551 00552 }// namespace Iii 00553 }// namespace OMK
Documentation generated on Mon Jun 9 11:45:56 2008 |
Generated with doxygen by Dimitri van Heesch , 1997-2007 |