curve.cxx

Go to the documentation of this file.
00001 /*=========================================================================
00002 
00003  Program:   wxMaracas
00004  Module:    $RCSfile: curve.cxx,v $
00005  Language:  C++
00006  Date:      $Date: 2009/05/14 13:55:07 $
00007  Version:   $Revision: 1.1 $
00008  
00009   Copyright: (c) 2002, 2003
00010   License:
00011   
00012    This software is distributed WITHOUT ANY WARRANTY; without even 
00013    the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
00014    PURPOSE.  See the above copyright notice for more information.
00015    
00016 =========================================================================*/
00017 
00018 // PS -> #include "gslobj.hxx" //PS
00019 #include "marMatrix.h"
00020 #include <math.h> //PS
00021 #include "marMathConst.h" //PS
00022 
00023 #include "curve.hxx"
00024 
00025 
00026 int kCurve::MAX_DIMENSION = 0;
00027 int kCurve::MAX_STATE_DIMENSION = 0;
00028 
00029 #define KCURVE_CHANGE_DIMENSIONS( ) \
00030     kCurve::MAX_DIMENSION = ( kCurve::MAX_DIMENSION > _dimension )?\
00031         kCurve::MAX_DIMENSION: _dimension;\
00032     kCurve::MAX_STATE_DIMENSION = ( kCurve::MAX_STATE_DIMENSION > _stateDim )?\
00033 kCurve::MAX_STATE_DIMENSION: _stateDim;
00034 
00035 // Catmull-Rom basic functions and their derivatives.
00036 
00037 // -------------------------------------------------------------------------
00038 void f_catmull( double* N, double t )
00039 {
00040     double t2 = t * t;
00041     double t3 = t2 * t;
00042         
00043     N[ 0 ] = -t3 + ( 2 * t2 ) - t;
00044     N[ 1 ] = ( 3 * t3 ) - ( 5 * t2 ) + 2;
00045     N[ 2 ] = -( 3 * t3 ) + ( 4 * t2 ) + t;
00046     N[ 3 ] = t3 - t2;
00047 }
00048 
00049 // -------------------------------------------------------------------------
00050 void f_d1_catmull( double* N, double t )
00051 {
00052     double t2 = t * t;
00053         
00054     N[ 0 ] = -( 3 * t2 ) + ( 4 * t ) - 1;
00055     N[ 1 ] = ( 9 * t2 ) - ( 10 * t );
00056     N[ 2 ] = -( 9 * t2 ) + ( 8 * t ) + 1;
00057     N[ 3 ] = ( 3 * t2 ) - ( 2 * t );
00058 }
00059 
00060 // -------------------------------------------------------------------------
00061 void f_d2_catmull( double* N, double t )
00062 {
00063     N[ 0 ] = -( 6 * t ) + 4;
00064     N[ 1 ] = ( 18 * t ) - 10;
00065     N[ 2 ] = -( 18 * t ) + 8;
00066     N[ 3 ] = ( 6 * t ) - 2;
00067 }
00068 
00069 // -------------------------------------------------------------------------
00070 kCurve::kCurve( size_t dim, size_t sdim )
00071 : _dimension( dim ), _stateDim( sdim )
00072 {
00073     _mC = new double[ _dimension * 4 ];
00074     _mS = new double[ _stateDim * 4 ];
00075     KCURVE_CHANGE_DIMENSIONS( );
00076 }
00077 
00078 // -------------------------------------------------------------------------
00079 kCurve::~kCurve( )
00080 {
00081     reset( );
00082     delete[] _mC;
00083     delete[] _mS;
00084 }
00085 
00086 // -------------------------------------------------------------------------
00087 kCurve& kCurve::operator=( const kCurve& r )
00088 {
00089     reset( );
00090     delete[] _mC;
00091     delete[] _mS;
00092         
00093     _dimension = r._dimension;
00094     _stateDim = r._stateDim;
00095         
00096     _mC = new double[ _dimension * 4 ];
00097     _mS = new double[ _stateDim * 4 ];
00098         
00099     for( int i = 0; i < r._controlPoints.size( ); i++ )
00100                 addControlPoint( r._controlPoints[ i ],
00101                 r._controlState[ i ] );
00102     return( *this );
00103 }
00104 
00105 // -------------------------------------------------------------------------
00106 uint kCurve::getClosestControlPoint( double* p )
00107 {
00108     int res;
00109 // PS ->     gslobj_vector vp( p, _dimension ), cp( _dimension ); //PS
00110         marVector vp (p,_dimension),cp(_dimension);
00111     double dist, min;
00112         
00113     cp = _controlPoints[ 0 ];
00114     min = ( cp - vp ).norm2( );
00115     res = 0;
00116         
00117     for( int i = 1; i < _controlPoints.size( ); i++ )
00118     {
00119                 cp = _controlPoints[ i ];
00120                 dist = ( cp - vp ).norm2( );
00121                 if( min > dist )
00122                 {
00123                         min = dist;
00124                         res = i;
00125                 } // fi
00126     } // rof
00127         
00128     return( res );
00129 }
00130 
00131 // -------------------------------------------------------------------------
00132 void kCurve::getPoint( double* p, double s )
00133 {
00134     double t;
00135     int i;
00136         
00137     calculeSplineArguments( s, i, t );
00138     evaluate( p, i, t );
00139 }
00140 
00141 // -------------------------------------------------------------------------
00142 void kCurve::getState( double* st, double s )
00143 {
00144     double t;
00145     int i;
00146         
00147     calculeSplineArguments( s, i, t );
00148     evalState( st, i, t );
00149 }
00150 
00151 // -------------------------------------------------------------------------
00152 void kCurve::getTangent( double* tg, double s )
00153 {
00154     double t;
00155     int i;
00156         
00157     calculeSplineArguments( s, i, t );
00158     derivative1( tg, i, t );
00159         
00160 // PS ->     gslobj_vector tmp( tg, _dimension ); //PS
00161         marVector tmp( tg, _dimension );
00162     memcpy( tg,
00163                 ( double* )( tmp.normalize( ) ),
00164                 _dimension * sizeof( double ) );
00165 }
00166 
00167 // -------------------------------------------------------------------------
00168 void kCurve::getNormal( double* n, double s )
00169 {
00170     double t;
00171     int i;
00172         
00173     calculeSplineArguments( s, i, t );
00174     derivative2( n, i, t );
00175         
00176 // PS ->     gslobj_vector tmp( n, _dimension ); //PS
00177         marVector tmp( n, _dimension );
00178     memcpy( n,
00179                 ( double* )( tmp.normalize( ) ),
00180                 _dimension * sizeof( double ) );
00181 }
00182 
00183 // -------------------------------------------------------------------------
00184 void kCurve::getBinormal( double* b, double s )
00185 {
00186 // PS ->     //gslobj_vector tg( _dimension ), n( _dimension ); //PS
00187         marVector tg( _dimension ), n( _dimension );
00188     double t;
00189     int i;
00190         
00191     calculeSplineArguments( s, i, t );
00192     derivative1( ( double* )tg, i, t );
00193     derivative2( ( double* )n, i, t );
00194         
00195     memcpy( b,
00196                 ( double* )( tg.cross( n ).normalize( ) ),
00197                 _dimension * sizeof( double ) );
00198 }
00199 
00200 // -------------------------------------------------------------------------
00201 void kCurve::addControlPoint( double* cp, double* sp )
00202 {
00203     double* tmp = new double[ _dimension ];
00204     memcpy( tmp, cp, _dimension * sizeof( double ) );
00205     _controlPoints.push_back( tmp );
00206     if( sp != NULL && _stateDim > 0 ) {
00207                 
00208                 double *tmp1 = new double[ _stateDim ];
00209                 memcpy( tmp1, sp, _stateDim * sizeof( double ) );
00210                 _controlState.push_back( tmp1 );
00211                 
00212     } else
00213                 _controlState.push_back( NULL );
00214         
00215     if( _controlPoints.size( ) == 1 ) {
00216                 
00217                 _controlT.push_back( 0.0 );
00218                 _controlL.push_back( 0.0 );
00219                 
00220     } else {
00221                 
00222                 // Here we maintain some extra info about the control points.
00223                 // These data will allow the high level programmer to think
00224                 // (if so ;-) just in terms of an arclength parameter "s".
00225 // PS ->                //gslobj_vector v1( _dimension ), v0( _dimension ); //PS
00226                 marVector v1( _dimension ), v0( _dimension );
00227                 double len;
00228                 int p;
00229                 
00230                 p = _controlPoints.size( ) - 1;
00231                 v1 = _controlPoints[ p ];
00232                 v0 = _controlPoints[ p - 1 ];
00233                 len = ( v1 - v0 ).norm2( ) + _controlL[ p - 1 ];
00234                 _controlL.push_back( len );
00235                 _controlT.push_back( 0.0 );
00236                 for( int i = 0; i < _controlT.size( ); i++ )
00237                         _controlT[ i ] = _controlL[ i ] / len;
00238                 
00239     }// fi
00240 }
00241 
00242 // -------------------------------------------------------------------------
00243 void kCurve::getControlPoint( int i, double* cp, double* sp )
00244 {
00245     memcpy( cp, _controlPoints[ i ], _dimension * sizeof( double ) );
00246     if( sp != NULL && _controlState[ i ] != NULL && _stateDim > 0 )
00247                 memcpy( sp, _controlState[ i ], _stateDim * sizeof( double ) );
00248 }
00249 
00250 // -------------------------------------------------------------------------
00251 void kCurve::setControlPoint( int i, double* cp, double* sp )
00252 {
00253     memcpy( _controlPoints[ i ], cp, _dimension * sizeof( double ) );
00254     if( sp != NULL && _stateDim > 0 )
00255                 memcpy( _controlState[ i ], sp, _stateDim * sizeof( double ) );
00256         
00257     if( _controlPoints.size( ) > 1 ) {
00258 // PS ->                //gslobj_vector v1( _dimension ), v0( _dimension ); //PS
00259                 marVector v1( _dimension ), v0( _dimension );
00260                 double len;
00261                 int it;
00262                 
00263                 for( it = i; it < _controlT.size( ); it++ ) {
00264                         
00265                         v1 = _controlPoints[ it ];
00266                         v0 = _controlPoints[ it - 1 ];
00267                         len = ( v1 - v0 ).norm2( ) + _controlL[ it - 1 ];
00268                         _controlL[ i ] = len;
00269                         
00270                 } // rof
00271                 
00272                 for( it = 0; it < _controlT.size( ); it++ )
00273                         _controlT[ it ] = _controlL[ it ] / len;
00274                 
00275     }// fi
00276 }
00277 
00278 // -------------------------------------------------------------------------
00279 double kCurve::length( double step )
00280 {
00281 // PS ->     //gslobj_vector nV( 4 ), q( _dimension ), p( _dimension ); //PS
00282         marVector nV( 4 ), q( _dimension ), p( _dimension );
00283 // PS ->     //gslobj_matrix mC( _mC, _dimension, 4 ); //PS
00284         marMatrix mC( _mC, _dimension, 4 );
00285     double l = 0;
00286         
00287     loadCatmullControlMatrix( 0 );
00288     f_catmull( ( double* )nV, 0 );
00289     p = ( mC * nV ) * 0.5;
00290         
00291     for( int i = 0; i < _controlPoints.size( ); i++ ) {
00292                 
00293                 loadCatmullControlMatrix( i );
00294                 for( double t = 0.0; t <= 1.0; t += step ) {
00295                         
00296                         f_catmull( ( double* )nV, t );
00297                         q = ( mC * nV ) * 0.5;
00298                         l += ( q - p ).norm2( );
00299                         p = q;
00300                         
00301                 } // rof
00302                 
00303     } // rof
00304     return( l );
00305 }
00306 
00307 // -------------------------------------------------------------------------
00308 double kCurve::projectOverControlPoints( double* pr, double* pt )
00309 {
00310 // PS ->     //gslobj_vector xpc( 3 ), xpo( pt, 3 ); //PS
00311         marVector xpc( 3 ), xpo( pt, 3 );
00312 // PS ->     //gslobj_vector xpa( 3 ), xpn( 3 ); //PS
00313         marVector xpa( 3 ), xpn( 3 ); 
00314 // PS ->     //gslobj_vector xpr( pr, 3 ); //PS
00315         marVector xpr( pr, 3 ); 
00316     double sina, sinn, cosa, cosn, tha, thn;
00317     double d, e, t, l, tca, tcn, lca, lcn;
00318     uint icp = getClosestControlPoint( pt );
00319         
00320     getControlPoint( icp, ( double* )xpc, NULL );
00321         
00322     if( icp == 0 ) {
00323                 
00324                 getControlPoint( icp + 1, ( double* )xpn, NULL );
00325                 
00326                 sinn = ( ( xpo - xpc ).cross( xpn - xpc ) ).norm2( ) /
00327                         ( ( xpo - xpc ).norm2( ) * ( xpn - xpc ).norm2( ) );
00328                 cosn = ( xpo - xpc ).dot( xpn - xpc ) /
00329                         ( ( xpo - xpc ).norm2( ) * ( xpn - xpc ).norm2( ) );
00330 
00331 // PS ->                //thn = acos( cosn ) + ( ( sinn >= 0 )? M_PI: 0 ); //PS
00332                 thn = acos( cosn ) + ( ( sinn >= 0 )? PI: 0 );
00333 // PS ->                //if( 0 <= thn && thn <= M_PI_2 ) { //PS
00334                 if( 0 <= thn && thn <= (PI/2) ) {
00335                         
00336                         tca = _controlT[ icp ];
00337                         lca = _controlL[ icp ];
00338                         tcn = _controlT[ icp + 1 ];
00339                         lcn = _controlL[ icp + 1 ];
00340                         xpa = xpc;
00341                         
00342                         d = ( ( xpn - xpa ).cross( xpa - xpo ) ).norm2( ) /
00343                                 ( xpn - xpa ).norm2( );
00344                         e = ( xpo - xpa ).norm2( );
00345                         e = ( e * e ) - ( d * d );
00346                         e = sqrt( e );
00347                         xpr = ( ( xpn - xpa ).normalize( ) * e ) + xpa;
00348                         l = ( xpr - xpa ).norm2( ) + lca;
00349                         t = ( ( ( tcn - tca ) / ( lcn - lca ) ) * ( l - lca ) ) + tca;
00350                         
00351                 } else {
00352                         
00353                         xpr = xpc;
00354                         t = 0;
00355                         
00356                 } // fi
00357                 
00358     } else if( icp == _controlPoints.size( ) - 1 ) {
00359                 
00360                 getControlPoint( icp - 1, ( double* )xpa, NULL );
00361                 
00362                 sina = ( ( xpa - xpc ).cross( xpo - xpc ) ).norm2( ) /
00363                         ( ( xpa - xpc ).norm2( ) * ( xpo - xpc ).norm2( ) );
00364                 cosa = ( xpa - xpc ).dot( xpo - xpc ) /
00365                         ( ( xpa - xpc ).norm2( ) * ( xpo - xpc ).norm2( ) );
00366                 
00367 // PS ->                //tha = acos( cosa ) + ( ( sina >= 0 )? M_PI: 0 ); //PS
00368                 tha = acos( cosa ) + ( ( sina >= 0 )? PI: 0 );
00369                 
00370 // PS ->                //if( 0 <= tha && tha <= M_PI_2 ) { //PS
00371                 if( 0 <= tha && tha <= (PI/2) ) {
00372                         
00373                         tca = _controlT[ icp - 1 ];
00374                         lca = _controlL[ icp - 1 ];
00375                         tcn = _controlT[ icp ];
00376                         lcn = _controlL[ icp ];
00377                         xpn = xpc;
00378                         
00379                         d = ( ( xpn - xpa ).cross( xpa - xpo ) ).norm2( ) /
00380                                 ( xpn - xpa ).norm2( );
00381                         e = ( xpo - xpa ).norm2( );
00382                         e = ( e * e ) - ( d * d );
00383                         e = sqrt( e );
00384                         xpr = ( ( xpn - xpa ).normalize( ) * e ) + xpa;
00385                         l = ( xpr - xpa ).norm2( ) + lca;
00386                         t = ( ( ( tcn - tca ) / ( lcn - lca ) ) * ( l - lca ) ) + tca;
00387                         
00388                 } else {
00389                         
00390                         xpr = xpc;
00391                         t = _controlT[ _controlPoints.size( ) - 1 ];
00392                         
00393                 } // fi
00394                 
00395     } else {
00396                 
00397                 getControlPoint( icp - 1, ( double* )xpa, NULL );
00398                 getControlPoint( icp + 1, ( double* )xpn, NULL );
00399                 
00400                 sina = ( ( xpa - xpc ).cross( xpo - xpc ) ).norm2( ) /
00401                         ( ( xpa - xpc ).norm2( ) * ( xpo - xpc ).norm2( ) );
00402                 sinn = ( ( xpo - xpc ).cross( xpn - xpc ) ).norm2( ) /
00403                         ( ( xpo - xpc ).norm2( ) * ( xpn - xpc ).norm2( ) );
00404                 cosa = ( xpa - xpc ).dot( xpo - xpc ) /
00405                         ( ( xpa - xpc ).norm2( ) * ( xpo - xpc ).norm2( ) );
00406                 cosn = ( xpo - xpc ).dot( xpn - xpc ) /
00407                         ( ( xpo - xpc ).norm2( ) * ( xpn - xpc ).norm2( ) );
00408                 
00409 // PS ->                //tha = acos( cosa ) + ( ( sina >= 0 )? M_PI: 0 );//PS
00410                 tha = acos( cosa ) + ( ( sina >= 0 )? PI: 0 );
00411 // PS ->                //thn = acos( cosn ) + ( ( sinn >= 0 )? M_PI: 0 );//PS
00412                 thn = acos( cosn ) + ( ( sinn >= 0 )? PI: 0 );
00413                 
00414                 if( tha < thn ) {
00415                         
00416                         tca = _controlT[ icp - 1 ];
00417                         lca = _controlL[ icp - 1 ];
00418                         tcn = _controlT[ icp ];
00419                         lcn = _controlL[ icp ];
00420                         xpn = xpc;
00421                         
00422                 } else {
00423                         
00424                         tca = _controlT[ icp ];
00425                         lca = _controlL[ icp ];
00426                         tcn = _controlT[ icp + 1 ];
00427                         lcn = _controlL[ icp + 1 ];
00428                         xpa = xpc;
00429                         
00430                 } // fi
00431                 
00432                 d = ( ( xpn - xpa ).cross( xpa - xpo ) ).norm2( ) /
00433                         ( xpn - xpa ).norm2( );
00434                 e = ( xpo - xpa ).norm2( );
00435                 e = ( e * e ) - ( d * d );
00436                 e = sqrt( e );
00437                 xpr = ( ( xpn - xpa ).normalize( ) * e ) + xpa;
00438                 l = ( xpr - xpa ).norm2( ) + lca;
00439                 t = ( ( ( tcn - tca ) / ( lcn - lca ) ) * ( l - lca ) ) + tca;
00440                 
00441     } // fi
00442         
00443     return( t );
00444 }
00445 
00446 // -------------------------------------------------------------------------
00447 double kCurve::projectOverCurve( double* pr, double* pt )
00448 { // TODO
00449     return( 0 );
00450 }
00451 
00452 // -------------------------------------------------------------------------
00453 void kCurve::evaluate( double* p, int i, double t )
00454 {
00455 // PS ->     //gslobj_vector nV( 4 ), q( _dimension ); //PS
00456         marVector nV( 4 ), q( _dimension ); 
00457 // PS ->     //gslobj_matrix mC( _mC, _dimension, 4 ); //PS
00458         marMatrix mC( _mC, _dimension, 4 ); 
00459         
00460     loadCatmullControlMatrix( i );
00461     f_catmull( ( double* )nV, t );
00462     q = ( mC * nV ) * 0.5;
00463     memcpy( p, ( double* )q, _dimension * sizeof( double ) );
00464 }
00465 
00466 // -------------------------------------------------------------------------
00467 void kCurve::evalState( double* s, int i, double t )
00468 {
00469 // PS ->     //gslobj_vector nV( 4 ), q( _stateDim ); //PS
00470         marVector nV( 4 ), q( _stateDim ); 
00471 // PS ->     //gslobj_matrix mS( _mS, _stateDim, 4 ); //PS
00472         marMatrix mS( _mS, _stateDim, 4 ); 
00473         
00474     loadCatmullStateMatrix( i );
00475     f_catmull( ( double* )nV, t );
00476     q = ( mS * nV ) * 0.5;
00477     memcpy( s, ( double* )q, _stateDim * sizeof( double ) );
00478 }
00479 
00480 // ---------------------------------------------------------------------------
00481 void kCurve::derivative1( double* d, int i, double t )
00482 {
00483 // PS ->     //gslobj_vector nV( 4 ), q( _dimension ); //PS
00484         marVector nV( 4 ), q( _dimension ); 
00485 // PS ->     //gslobj_matrix mC( _mC, _dimension, 4 ); //PS
00486         marMatrix mC( _mC, _dimension, 4 );
00487         
00488     loadCatmullControlMatrix( i );
00489     f_d1_catmull( ( double* )nV, t );
00490     q = ( mC * nV ) * 0.5;
00491     memcpy( d, ( double* )q, _dimension * sizeof( double ) );
00492 }
00493 
00494 // -------------------------------------------------------------------------
00495 void kCurve::derivative2( double* d, int i, double t )
00496 {
00497 // PS ->     //gslobj_vector nV( 4 ), q( _dimension ); //PS
00498         marVector nV( 4 ), q( _dimension ); 
00499 // PS ->     //gslobj_matrix mC( _mC, _dimension, 4 ); //PS
00500         marMatrix mC( _mC, _dimension, 4 ); 
00501         
00502     loadCatmullControlMatrix( i );
00503     f_d2_catmull( ( double* )nV, t );
00504     q = ( mC * nV ) * 0.5;
00505     memcpy( d, ( double* )q, _dimension * sizeof( double ) );
00506 }
00507 
00508 // -------------------------------------------------------------------------
00509 void kCurve::reset( )
00510 {
00511     int i;
00512         
00513     for( i = 0; i < _controlPoints.size( ); i++ )
00514                 delete[] _controlPoints[ i ];
00515     _controlPoints.clear( );
00516         
00517     for( i = 0; i < _controlState.size( ); i++ )
00518                 if( _controlState[ i ] != NULL ) delete[] _controlState[ i ];
00519                 _controlState.clear( );
00520                 
00521                 _controlT.clear( );
00522                 _controlL.clear( );
00523 }
00524 
00525 // -------------------------------------------------------------------------
00526 void kCurve::loadCatmullControlMatrix( int i )
00527 {
00528 // PS ->     //gslobj_matrix mC( _mC, _dimension, 4 ); //PS
00529         marMatrix mC( _mC, _dimension, 4 ); 
00530     int l;
00531         
00532     for( int j = i - 1; j <= i + 2; j++ ) {
00533                 
00534                 for( int k = 0; k < _dimension; k++ ) {
00535                         
00536                         l = ( j >= 0 )? j: 0;
00537                         l = ( l >= _controlPoints.size( ) )?
00538                                 _controlPoints.size( ) - 1: l;
00539                         mC( k, j - i + 1 ) = _controlPoints[ l ][ k ];
00540                         
00541                 } // rof
00542                 
00543     } // rof
00544 }
00545 
00546 // -------------------------------------------------------------------------
00547 void kCurve::loadCatmullStateMatrix( int i )
00548 {
00549 // PS ->     //gslobj_matrix mS( _mS, _stateDim, 4 ); //PS
00550         marMatrix mS( _mS, _stateDim, 4 ); 
00551     int l;
00552         
00553     for( int j = i - 1; j <= i + 2; j++ ) {
00554                 
00555                 for( int k = 0; k < _stateDim; k++ ) {
00556                         
00557                         l = ( j >= 0 )? j: 0;
00558                         l = ( l >= _controlState.size( ) )?
00559                                 _controlState.size( ) - 1: l;
00560                         mS( k, j - i + 1 ) = _controlState[ l ][ k ];
00561                         
00562                 } // rof
00563                 
00564     } // rof
00565 }
00566 
00567 // -------------------------------------------------------------------------
00568 void kCurve::calculeSplineArguments( double s, int& i, double& t )
00569 {
00570     for( i = 0; i < _controlT.size( ) && _controlT[ i ] <= s; i++ );
00571         
00572     if( s < 1.0 ) i--;
00573         
00574     t = ( ( _controlL[ _controlL.size( ) - 1 ] * s ) - _controlL[ i ] ) /
00575                 ( _controlL[ i + 1 ] - _controlL[ i ] );
00576     t = ( s < 1.0 )? t: 1.0;
00577     i = ( s < 1.0 )? i: _controlT.size( ) - 1;
00578 }
00579 
00580 // eof - curve.cxx

Generated on 18 Mar 2010 for creaMaracasVisu_lib by  doxygen 1.6.1