osgEarth 2.1.1

/home/cube/sources/osgearth/src/osgEarthUtil/EarthManipulator.cpp

Go to the documentation of this file.
00001 /* -*-c++-*- */
00002 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
00003  * Copyright 2008-2010 Pelican Mapping
00004  * http://osgearth.org
00005  *
00006  * osgEarth is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU Lesser General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program 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 Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public License
00017  * along with this program.  If not, see <http://www.gnu.org/licenses/>
00018  */
00019 #include <osgEarthUtil/EarthManipulator>
00020 #include <osgEarth/FindNode>
00021 #include <osg/Quat>
00022 #include <osg/Notify>
00023 #include <osg/MatrixTransform>
00024 #include <osgUtil/LineSegmentIntersector>
00025 #include <osgViewer/View>
00026 #include <iomanip>
00027 
00028 #include <osg/io_utils>
00029 
00030 #define LC "[EarthManip] "
00031 
00032 using namespace osgEarth::Util;
00033 using namespace osgEarth;
00034 
00035 /****************************************************************************/
00036 
00037 
00038 namespace
00039 {
00040     // a reasonable approximation of cosine interpolation
00041     double
00042     smoothStepInterp( double t ) {
00043         return (t*t)*(3.0-2.0*t);
00044     }
00045 
00046     // rough approximation of pow(x,y)
00047     double
00048     powFast( double x, double y ) {
00049         return x/(x+y-y*x);
00050     }
00051 
00052     // accel/decel curve (a < 0 => decel)
00053     double
00054     accelerationInterp( double t, double a ) {
00055         return a == 0.0? t : a > 0.0? powFast( t, a ) : 1.0 - powFast(1.0-t, -a);
00056     }
00057     
00058     void
00059     s_getHPRFromQuat(const osg::Quat& q, double &h, double &p, double &r)
00060     {
00061 #if 0
00062         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm
00063 
00064         p = atan2(2*(q.y()*q.z() + q.w()*q.x()), (q.w()*q.w() - q.x()*q.x() - q.y()*q.y() + q.z() * q.z()));
00065             h = asin (2*q.x()*q.y() + 2*q.z()*q.w());
00066         r = atan2(2*q.x()*q.w()-2*q.y()*q.z() , 1 - 2*q.x()*q.x() - 2*q.z()*q.z());
00067         
00068         if( osg::equivalent( q.x()*q.y() + q.z() *q.w(), 0.5 ) )
00069             { 
00070                     p = (float)(2 * atan2( q.x(),q.w())); 
00071                     r = 0;     
00072             }    
00073         else if( osg::equivalent( q.x()*q.y() + q.z()*q.w(), -0.5 ) )
00074             { 
00075                     p = (float)(-2 * atan2(q.x(), q.w())); 
00076                     r = 0; 
00077             } 
00078      
00079 #else
00080         osg::Matrixd rot(q);
00081         p = asin(rot(1,2));
00082         if( osg::equivalent(osg::absolute(p), osg::PI_2) )
00083         {
00084             r = 0.0;
00085             h = atan2( rot(0,1), rot(0,0) );
00086         }
00087         else
00088         {
00089             r = atan2( rot(0,2), rot(2,2) );
00090             h = atan2( rot(1,0), rot(1,1) );
00091         }
00092 #endif
00093     }
00094 }
00095 
00096 
00097 /****************************************************************************/
00098 
00099 EarthManipulator::Action::Action( ActionType type, const ActionOptions& options ) :
00100 _type( type ),
00101 _options( options )
00102 { 
00103     init();
00104 }
00105 
00106 EarthManipulator::Action::Action( ActionType type ) :
00107 _type( type )
00108 {
00109     init();
00110 }
00111 
00112 void
00113 EarthManipulator::Action::init()
00114 {
00115     _dir =
00116         _type == ACTION_PAN_LEFT  || _type == ACTION_ROTATE_LEFT? DIR_LEFT :
00117         _type == ACTION_PAN_RIGHT || _type == ACTION_ROTATE_RIGHT? DIR_RIGHT :
00118         _type == ACTION_PAN_UP    || _type == ACTION_ROTATE_UP   || _type == ACTION_ZOOM_IN ? DIR_UP :
00119         _type == ACTION_PAN_DOWN  || _type == ACTION_ROTATE_DOWN || _type == ACTION_ZOOM_OUT ? DIR_DOWN :
00120         DIR_NA;
00121 }
00122 
00123 EarthManipulator::Action::Action( const Action& rhs ) :
00124 _type( rhs._type ),
00125 _dir( rhs._dir ),
00126 _options( rhs._options )
00127 {
00128     //nop
00129 }
00130 
00131 bool 
00132 EarthManipulator::Action::getBoolOption( int option, bool defaultValue ) const
00133 {
00134     for(ActionOptions::const_iterator i = _options.begin(); i != _options.end(); i++ ) {
00135         if ( i->option() == option )
00136             return i->boolValue();
00137     }
00138     return defaultValue;
00139 }
00140 
00141 int 
00142 EarthManipulator::Action::getIntOption( int option, int defaultValue ) const
00143 {
00144     for(ActionOptions::const_iterator i = _options.begin(); i != _options.end(); i++ ) {
00145         if ( i->option() == option )
00146             return i->intValue();
00147     }
00148     return defaultValue;
00149 }
00150 
00151 double 
00152 EarthManipulator::Action::getDoubleOption( int option, double defaultValue ) const
00153 {
00154     for(ActionOptions::const_iterator i = _options.begin(); i != _options.end(); i++ ) {
00155         if ( i->option() == option )
00156             return i->doubleValue();
00157     }
00158     return defaultValue;
00159 }
00160 
00161 /****************************************************************************/
00162 
00163 EarthManipulator::Action EarthManipulator::NullAction( EarthManipulator::ACTION_NULL );
00164 
00165 static std::string s_actionNames[] = {
00166     "null",
00167     "home",
00168     "goto",
00169     "pan",
00170     "pan-left",
00171     "pan-right",
00172     "pan-up",
00173     "pan-down",
00174     "rotate",
00175     "rotate-left",
00176     "rotate-right",
00177     "rotate-up",
00178     "rotate-down",
00179     "zoom",
00180     "zoom-in",
00181     "zoom-out",
00182     "earth-drag"
00183 };
00184 
00185 static std::string s_actionOptionNames[] = {
00186     "scale-x",
00187     "scale-y",
00188     "continuous",
00189     "single-axis",
00190     "goto-range-factor",
00191     "duration"
00192 };
00193 
00194 static short s_actionOptionTypes[] = { 1, 1, 0, 0, 1, 1 }; // 0=bool, 1=double
00195 
00196 //------------------------------------------------------------------------
00197 
00198 EarthManipulator::Settings::Settings() :
00199 _single_axis_rotation( false ),
00200 _throwing( false ),
00201 _lock_azim_while_panning( true ),
00202 _mouse_sens( 1.0 ),
00203 _keyboard_sens( 1.0 ),
00204 _scroll_sens( 1.0 ),
00205 _min_pitch( -89.9 ),
00206 _max_pitch( -10.0 ),
00207 _max_x_offset( 0.0 ),
00208 _max_y_offset( 0.0 ),
00209 _min_distance( 0.001 ),
00210 _max_distance( DBL_MAX ),
00211 _tether_mode( TETHER_CENTER ),
00212 _arc_viewpoints( false ),
00213 _auto_vp_duration( false ),
00214 _min_vp_duration_s( 3.0 ),
00215 _max_vp_duration_s( 8.0 )
00216 {
00217     //NOP
00218 }
00219 
00220 EarthManipulator::Settings::Settings( const EarthManipulator::Settings& rhs ) :
00221 _bindings( rhs._bindings ),
00222 _single_axis_rotation( rhs._single_axis_rotation ),
00223 _throwing( rhs._throwing ),
00224 _lock_azim_while_panning( rhs._lock_azim_while_panning ),
00225 _mouse_sens( rhs._mouse_sens ),
00226 _keyboard_sens( rhs._keyboard_sens ),
00227 _scroll_sens( rhs._scroll_sens ),
00228 _min_pitch( rhs._min_pitch ),
00229 _max_pitch( rhs._max_pitch ),
00230 _max_x_offset( rhs._max_x_offset ),
00231 _max_y_offset( rhs._max_y_offset ),
00232 _min_distance( rhs._min_distance ),
00233 _max_distance( rhs._max_distance ),
00234 _tether_mode( rhs._tether_mode ),
00235 _arc_viewpoints( rhs._arc_viewpoints ),
00236 _auto_vp_duration( rhs._auto_vp_duration ),
00237 _min_vp_duration_s( rhs._min_vp_duration_s ),
00238 _max_vp_duration_s( rhs._max_vp_duration_s )
00239 {
00240     //NOP
00241 }
00242 
00243 #define HASMODKEY( W, V ) (( W & V ) == V )
00244 
00245 // expands one input spec into many if necessary, to deal with modifier key combos.
00246 void
00247 EarthManipulator::Settings::expandSpec( const InputSpec& input, InputSpecs& output ) const
00248 {
00249     int e = input._event_type;
00250     int i = input._input_mask;
00251     int m = input._modkey_mask;
00252 
00253     if ( HASMODKEY(m, osgGA::GUIEventAdapter::MODKEY_CTRL) )
00254     {
00255         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_LEFT_CTRL ), output );
00256         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_RIGHT_CTRL ), output );
00257     }
00258     else if ( HASMODKEY(m, osgGA::GUIEventAdapter::MODKEY_ALT) )
00259     {
00260         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_LEFT_ALT ), output );
00261         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_RIGHT_ALT ), output );
00262     }
00263     else if ( HASMODKEY(m, osgGA::GUIEventAdapter::MODKEY_SHIFT) )
00264     {
00265         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_LEFT_SHIFT ), output );
00266         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_RIGHT_SHIFT ), output );
00267     }
00268     else if ( HASMODKEY(m, osgGA::GUIEventAdapter::MODKEY_META) )
00269     {
00270         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_LEFT_META ), output );
00271         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_RIGHT_META ), output );
00272     }
00273     else if ( HASMODKEY(m, osgGA::GUIEventAdapter::MODKEY_HYPER) )
00274     {
00275         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_LEFT_HYPER ), output );
00276         expandSpec( InputSpec( e, i, m & ~osgGA::GUIEventAdapter::MODKEY_RIGHT_HYPER ), output );
00277     }
00278 
00279     //Always add the input so if we are dealing with a windowing system like QT that just sends MODKEY_CTRL it will still work.
00280     output.push_back( input );
00281 }
00282 
00283 void
00284 EarthManipulator::Settings::bind( const InputSpec& spec, const Action& action )
00285 {
00286     InputSpecs specs;
00287     expandSpec( spec, specs );
00288     for( InputSpecs::const_iterator i = specs.begin(); i != specs.end(); i++ )
00289     {
00290         _bindings[*i] = action; //ActionBinding(*i, action);
00291     }
00292         //_bindings.push_back( ActionBinding( *i, action ) );
00293 }
00294 
00295 void
00296 EarthManipulator::Settings::bindMouse(ActionType actionType,
00297                                       int button_mask, int modkey_mask,
00298                                       const ActionOptions& options)
00299 {
00300     bind(
00301         InputSpec( osgGA::GUIEventAdapter::DRAG, button_mask, modkey_mask ),
00302         Action( actionType, options ) );
00303 }
00304 
00305 void
00306 EarthManipulator::Settings::bindMouseClick(ActionType action,
00307                                            int button_mask, int modkey_mask,
00308                                            const ActionOptions& options)
00309 {
00310     bind(
00311         InputSpec( EVENT_MOUSE_CLICK, button_mask, modkey_mask ),
00312         Action( action, options ) );
00313 }
00314 
00315 void
00316 EarthManipulator::Settings::bindMouseDoubleClick(ActionType action,
00317                                                  int button_mask, int modkey_mask,
00318                                                  const ActionOptions& options)
00319 {
00320     bind(
00321         InputSpec( EVENT_MOUSE_DOUBLE_CLICK, button_mask, modkey_mask ),
00322         Action( action, options ) );
00323 }
00324 
00325 void
00326 EarthManipulator::Settings::bindKey(ActionType action,
00327                                     int key, int modkey_mask,
00328                                     const ActionOptions& options)
00329 {
00330     bind(
00331         InputSpec( osgGA::GUIEventAdapter::KEYDOWN, key, modkey_mask ),
00332         Action( action, options ) );
00333 }
00334 
00335 void
00336 EarthManipulator::Settings::bindScroll(ActionType action, int scrolling_motion,
00337                                        int modkey_mask, const ActionOptions& options )
00338 {
00339     bind(
00340         InputSpec ( osgGA::GUIEventAdapter::SCROLL, scrolling_motion, modkey_mask ),
00341         Action( action, options ) );
00342 }
00343 
00344 const EarthManipulator::Action&
00345 EarthManipulator::Settings::getAction(int event_type, int input_mask, int modkey_mask) const
00346 {
00347     InputSpec spec( event_type, input_mask, modkey_mask );
00348     ActionBindings::const_iterator i = _bindings.find(spec);
00349     return i != _bindings.end() ? i->second : NullAction;
00350     //for( ActionBindings::const_iterator i = _bindings.begin(); i != _bindings.end(); i++ )
00351     //    if ( i->first == spec )
00352     //        return i->second;
00353     //return NullAction;
00354 }
00355 
00356 void
00357 EarthManipulator::Settings::setMinMaxPitch( double min_pitch, double max_pitch )
00358 {
00359     _min_pitch = osg::clampBetween( min_pitch, -89.9, 89.0 );
00360     _max_pitch = osg::clampBetween( max_pitch, min_pitch, 89.0 );
00361 }
00362 
00363 void
00364 EarthManipulator::Settings::setMaxOffset(double max_x_offset, double max_y_offset)
00365 {
00366         _max_x_offset = max_x_offset;
00367         _max_y_offset = max_y_offset;
00368 }
00369 
00370 void
00371 EarthManipulator::Settings::setMinMaxDistance( double min_distance, double max_distance)
00372 {
00373         _min_distance = min_distance;
00374         _max_distance = max_distance;
00375 }
00376 
00377 void
00378 EarthManipulator::Settings::setArcViewpointTransitions( bool value )
00379 {
00380     _arc_viewpoints = value;
00381 }
00382 
00383 void
00384 EarthManipulator::Settings::setAutoViewpointDurationEnabled( bool value )
00385 {
00386     _auto_vp_duration = value;
00387 }
00388 
00389 void
00390 EarthManipulator::Settings::setAutoViewpointDurationLimits( double minSeconds, double maxSeconds )
00391 {
00392     _min_vp_duration_s = osg::clampAbove( minSeconds, 0.0 );
00393     _max_vp_duration_s = osg::clampAbove( maxSeconds, _min_vp_duration_s );
00394 }
00395 
00396 /************************************************************************/
00397 
00398 
00399 EarthManipulator::EarthManipulator() :
00400 _last_action( ACTION_NULL )
00401 {
00402     reinitialize();
00403     configureDefaultSettings();
00404 }
00405 
00406 EarthManipulator::EarthManipulator( const EarthManipulator& rhs ) :
00407 _thrown( rhs._thrown ),
00408 _distance( rhs._distance ),
00409 _offset_x( rhs._offset_x ),
00410 _offset_y( rhs._offset_y ),
00411 _continuous( rhs._continuous ),
00412 _task( new Task() ),
00413 _settings( new Settings( *rhs._settings.get() ) ),
00414 _srs_lookup_failed( rhs._srs_lookup_failed ),
00415 _last_action( rhs._last_action ),
00416 _setting_viewpoint( rhs._setting_viewpoint ),
00417 _delta_t( rhs._delta_t ),
00418 _t_factor( rhs._t_factor ),
00419 _time_s_last_frame( rhs._time_s_last_frame  ),
00420 _local_azim( rhs._local_azim ),
00421 _local_pitch( rhs._local_pitch  ),
00422 _has_pending_viewpoint( rhs._has_pending_viewpoint ),
00423 _homeViewpoint( rhs._homeViewpoint.get() ),
00424 _homeViewpointDuration( rhs._homeViewpointDuration ),
00425 _after_first_frame( rhs._after_first_frame ),
00426 _lastPointOnEarth( rhs._lastPointOnEarth ),
00427 _arc_height( rhs._arc_height )
00428 {
00429 }
00430 
00431 
00432 EarthManipulator::~EarthManipulator()
00433 {
00434     //NOP
00435 }
00436 
00437 void
00438 EarthManipulator::configureDefaultSettings()
00439 {
00440     _settings = new Settings();
00441 
00442     // install default action bindings:
00443     ActionOptions options;
00444 
00445     _settings->bindKey( ACTION_HOME, osgGA::GUIEventAdapter::KEY_Space );
00446 
00447     _settings->bindMouse( ACTION_PAN, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON );
00448     //_settings->bindMouse( ACTION_EARTH_DRAG, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON );
00449 
00450     // zoom as you hold the right button:
00451     options.clear();
00452     options.add( OPTION_CONTINUOUS, true );
00453     _settings->bindMouse( ACTION_ZOOM, osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON, 0L, options );
00454 
00455     // rotate with either the middle button or the left+right buttons:
00456     _settings->bindMouse( ACTION_ROTATE, osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON );
00457     _settings->bindMouse( ACTION_ROTATE, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON | osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON );
00458 
00459     // zoom with the scroll wheel:
00460     _settings->bindScroll( ACTION_ZOOM_IN,  osgGA::GUIEventAdapter::SCROLL_DOWN );
00461     _settings->bindScroll( ACTION_ZOOM_OUT, osgGA::GUIEventAdapter::SCROLL_UP );
00462 
00463     // pan around with arrow keys:
00464     _settings->bindKey( ACTION_PAN_LEFT,  osgGA::GUIEventAdapter::KEY_Left );
00465     _settings->bindKey( ACTION_PAN_RIGHT, osgGA::GUIEventAdapter::KEY_Right );
00466     _settings->bindKey( ACTION_PAN_UP,    osgGA::GUIEventAdapter::KEY_Up );
00467     _settings->bindKey( ACTION_PAN_DOWN,  osgGA::GUIEventAdapter::KEY_Down );
00468 
00469     // double click the left button to zoom in on a point:
00470     options.clear();
00471     options.add( OPTION_GOTO_RANGE_FACTOR, 0.4 );
00472     _settings->bindMouseDoubleClick( ACTION_GOTO, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON, 0L, options );
00473 
00474     // double click the right button (or CTRL-left button) to zoom out to a point
00475     options.clear();
00476     options.add( OPTION_GOTO_RANGE_FACTOR, 2.5 );
00477     _settings->bindMouseDoubleClick( ACTION_GOTO, osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON, 0L, options );
00478     _settings->bindMouseDoubleClick( ACTION_GOTO, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON, osgGA::GUIEventAdapter::MODKEY_CTRL, options );
00479 
00480     _settings->setThrowingEnabled( false );
00481     _settings->setLockAzimuthWhilePanning( true );
00482 }
00483 
00484 void
00485 EarthManipulator::applySettings( Settings* settings )
00486 {
00487     if ( settings )
00488     {
00489         _settings = settings;
00490     }
00491     else
00492     {
00493         configureDefaultSettings();
00494     }
00495 
00496     _task->_type = TASK_NONE;
00497     flushMouseEventStack();
00498 
00499     // apply new pitch restrictions
00500     double old_pitch = osg::RadiansToDegrees( _local_pitch );
00501     double new_pitch = osg::clampBetween( old_pitch, _settings->getMinPitch(), _settings->getMaxPitch() );
00502         setDistance(_distance);
00503 
00504     if ( new_pitch != old_pitch )
00505     {
00506         Viewpoint vp = getViewpoint();
00507         setViewpoint( Viewpoint(vp.getFocalPoint(), vp.getHeading(), new_pitch, vp.getRange(), vp.getSRS()) );
00508     }
00509 }
00510 
00511 EarthManipulator::Settings*
00512 EarthManipulator::getSettings() const
00513 {
00514     return _settings.get();
00515 }
00516 
00517 void
00518 EarthManipulator::reinitialize()
00519 {
00520     _distance = 1.0;
00521     _offset_x = 0.0;
00522     _offset_y = 0.0;
00523     _thrown = false;
00524     _continuous = false;
00525     _task = new Task();
00526     _last_action = ACTION_NULL;
00527     _srs_lookup_failed = false;
00528     _setting_viewpoint = false;
00529     _delta_t = 0.0;
00530     _t_factor = 1.0;
00531     _local_azim = 0.0;
00532     _local_pitch = 0.0;
00533     _has_pending_viewpoint = false;
00534     _lastPointOnEarth.set(0.0, 0.0, 0.0);
00535     _arc_height = 0.0;
00536 }
00537 
00538 bool
00539 EarthManipulator::established()
00540 {
00541 #ifdef USE_OBSERVER_NODE_PATH
00542     bool needToReestablish = (!_csn.valid() || _csnObserverPath.empty()) && _node.valid();
00543 #else
00544     bool needToReestablish = !_csn.valid() && _node.valid();
00545 #endif
00546 
00547     if ( needToReestablish )
00548     {
00549         osg::ref_ptr<osg::Node> safeNode = _node.get();
00550         if ( !safeNode.valid() )
00551             return false;
00552         
00553         // check the kids, then the parents
00554         osg::ref_ptr<osg::CoordinateSystemNode> csn = osgEarth::findTopMostNodeOfType<osg::CoordinateSystemNode>( safeNode.get() );    
00555         if ( !csn.valid() )
00556             csn = osgEarth::findFirstParentOfType<osg::CoordinateSystemNode>( safeNode.get() );        
00557 
00558         if ( csn.valid() )
00559         {
00560             _csn = csn.get();
00561             _node = csn.get();
00562 
00563 #if USE_OBSERVER_NODE_PATH
00564             _csnObserverPath.setNodePathTo( csn.get() );
00565 #endif
00566 
00567             if ( !_homeViewpoint.isSet() )
00568             {
00569                 if ( _has_pending_viewpoint )
00570                 {
00571                     setHomeViewpoint(
00572                         _pending_viewpoint,
00573                         _pending_viewpoint_duration_s );
00574 
00575                     _has_pending_viewpoint = false;
00576                 }
00577 
00578                 //If we have a CoordinateSystemNode and it has an ellipsoid model
00579                 if ( csn->getEllipsoidModel() )
00580                 {
00581                     setHomeViewpoint( 
00582                         Viewpoint(osg::Vec3d(-90,0,0), 0, -89,
00583                         csn->getEllipsoidModel()->getRadiusEquator()*3.0 ) );
00584                 }
00585                 else
00586                 {
00587                     setHomeViewpoint( Viewpoint(
00588                         safeNode->getBound().center(),
00589                         0, -89.9, 
00590                         safeNode->getBound().radius()*2.0) );
00591                 }
00592             }
00593 
00594             if ( !_has_pending_viewpoint )
00595                 setViewpoint( _homeViewpoint.get(), _homeViewpointDuration );
00596             else
00597                 setViewpoint( _pending_viewpoint, _pending_viewpoint_duration_s );
00598 
00599             _has_pending_viewpoint = false;
00600         }
00601 
00602         //if (getAutoComputeHomePosition()) computeHomePosition();
00603 
00604         // reset the srs cache:
00605         _cached_srs = NULL;
00606         _srs_lookup_failed = false;
00607 
00608         // track the local angles.
00609         recalculateLocalPitchAndAzimuth();
00610 
00611         //OE_DEBUG << "[EarthManip] new CSN established." << std::endl;
00612     }
00613 
00614     return _csn.valid() && _node.valid();
00615 }
00616 
00617 // This is code taken from osgViewer::View and placed here so that we can control the
00618 // caching of the CSNPath ourselves without relying on View. This helps when switching
00619 // out the Manipulator's Node.
00620 osg::CoordinateFrame
00621 EarthManipulator::getMyCoordinateFrame( const osg::Vec3d& position ) const
00622 {
00623     osg::CoordinateFrame coordinateFrame;
00624 
00625     osg::ref_ptr<osg::CoordinateSystemNode> csnSafe = _csn.get();
00626 
00627     if ( csnSafe.valid() )
00628     {
00629 #ifdef USE_OBSERVER_NODE_PATH
00630         if ( _csnObserverPath.empty() )
00631         {
00632             const_cast<EarthManipulator*>(this)->_csnObserverPath.setNodePathTo( csnSafe.get() );
00633             _csnObserverPath.getNodePath( const_cast<EarthManipulator*>(this)->_csnPath );
00634         }
00635 #else
00636         const_cast<EarthManipulator*>(this)->_csnPath = csnSafe->getParentalNodePaths()[0];
00637 #endif
00638 
00639         osg::Vec3 local_position = position * osg::computeWorldToLocal( _csnPath );
00640 
00641         // get the coordinate frame in world coords.
00642         coordinateFrame = csnSafe->computeLocalCoordinateFrame( local_position ) * osg::computeLocalToWorld( _csnPath );
00643 
00644         // keep the position of the coordinate frame to reapply after rescale.
00645         osg::Vec3d pos = coordinateFrame.getTrans();
00646 
00647         // compensate for any scaling, so that the coordinate frame is a unit size
00648         osg::Vec3d x(1.0,0.0,0.0);
00649         osg::Vec3d y(0.0,1.0,0.0);
00650         osg::Vec3d z(0.0,0.0,1.0);
00651         x = osg::Matrixd::transform3x3(x,coordinateFrame);
00652         y = osg::Matrixd::transform3x3(y,coordinateFrame);
00653         z = osg::Matrixd::transform3x3(z,coordinateFrame);
00654         coordinateFrame.preMultScale( osg::Vec3d(1.0/x.length(),1.0/y.length(),1.0/z.length()) );
00655 
00656         // reapply the position.
00657         coordinateFrame.setTrans( pos );
00658     }
00659     else
00660     {
00661         coordinateFrame = osg::computeLocalToWorld( _csnPath );
00662     }
00663 
00664     return coordinateFrame;
00665 }
00666 
00667 void
00668 EarthManipulator::setNode(osg::Node* node)
00669 {
00670     // you can only set the node if it has not already been set, OR if you are setting
00671     // it to NULL. (So to change it, you must first set it to NULL.) This is to prevent
00672     // OSG from overwriting the node after you have already set on manually.
00673     if ( node == 0L || !_node.valid() )
00674     {
00675         _node = node;
00676         _csn = 0L;
00677 #ifdef USE_OBSERVER_NODE_PATH
00678         _csnObserverPath.clearNodePath();
00679 #endif
00680         _csnPath.clear();
00681         reinitialize();
00682 
00683         // this might be unnecessary..
00684         established();
00685     }
00686 }
00687 
00688 osg::Node*
00689 EarthManipulator::getNode()
00690 {
00691     return _node.get();
00692 }
00693 
00694 const osgEarth::SpatialReference*
00695 EarthManipulator::getSRS() const
00696 {
00697     osg::ref_ptr<osg::Node> safeNode = _node.get();
00698 
00699     if ( !_cached_srs.valid() && !_srs_lookup_failed && safeNode.valid() )
00700     {
00701         EarthManipulator* nonconst_this = const_cast<EarthManipulator*>(this);
00702 
00703         nonconst_this->_is_geocentric = false;
00704 
00705         // first try to find a map node:
00706         osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode( safeNode.get() );       
00707         if ( mapNode )
00708         {
00709             nonconst_this->_cached_srs = mapNode->getMap()->getProfile()->getSRS();
00710             nonconst_this->_is_geocentric = mapNode->isGeocentric();
00711         }
00712 
00713         // if that doesn't work, try gleaning info from a CSN:
00714         if ( !_cached_srs.valid() )
00715         {
00716             osg::CoordinateSystemNode* csn = osgEarth::findTopMostNodeOfType<osg::CoordinateSystemNode>( safeNode.get() );
00717             if ( csn )
00718             {
00719                 nonconst_this->_cached_srs = osgEarth::SpatialReference::create( csn );
00720                 nonconst_this->_is_geocentric = csn->getEllipsoidModel() != NULL;
00721             }
00722         }
00723 
00724         nonconst_this->_srs_lookup_failed = !_cached_srs.valid();
00725 
00726         if ( _cached_srs.valid() )
00727         {
00728             OE_INFO << "[EarthManip] cached SRS: "
00729                 << _cached_srs->getName()
00730                 << ", geocentric=" << _is_geocentric
00731                 << std::endl;
00732         }
00733     }
00734 
00735     return _cached_srs.get();
00736 }
00737 
00738 
00739 static double
00740 normalizeAzimRad( double input ) {
00741         if(fabs(input) > 2*osg::PI)
00742                 input = fmod(input,2*osg::PI);
00743     if( input < -osg::PI ) input += osg::PI*2.0;
00744     if( input > osg::PI ) input -= osg::PI*2.0;
00745     return input;
00746 }
00747 
00748 osg::Matrixd
00749 EarthManipulator::getRotation(const osg::Vec3d& point) const
00750 {
00751     //The look vector will be going directly from the eye point to the point on the earth,
00752     //so the look vector is simply the up vector at the center point
00753     osg::CoordinateFrame cf = getMyCoordinateFrame( point ); //getCoordinateFrame(point);
00754     osg::Vec3d lookVector = -getUpVector(cf);
00755 
00756     osg::Vec3d side;
00757 
00758     //Force the side vector to be orthogonal to north
00759     osg::Vec3d worldUp(0,0,1);
00760 
00761     double dot = osg::absolute(worldUp * lookVector);
00762     if (osg::equivalent(dot, 1.0))
00763     {
00764         //We are looking nearly straight down the up vector, so use the Y vector for world up instead
00765         worldUp = osg::Vec3d(0, 1, 0);
00766         //OE_NOTICE << "using y vector victor" << std::endl;
00767     }
00768 
00769     side = lookVector ^ worldUp;
00770     osg::Vec3d up = side ^ lookVector;
00771     up.normalize();
00772 
00773     //We want a very slight offset
00774     double offset = 1e-6;
00775 
00776     return osg::Matrixd::lookAt( point - (lookVector * offset), point, up);
00777 }
00778 
00779 void
00780 EarthManipulator::setViewpoint( const Viewpoint& vp, double duration_s )
00781 {
00782     if ( !established() ) // !_node.valid() ) // || !_after_first_frame )
00783     {
00784         _pending_viewpoint = vp;
00785         _pending_viewpoint_duration_s = duration_s;
00786         _has_pending_viewpoint = true;
00787     }
00788 
00789     else if ( duration_s > 0.0 )
00790     {
00791         _start_viewpoint = getViewpoint();
00792         
00793         _delta_heading = vp.getHeading() - _start_viewpoint.getHeading(); //TODO: adjust for crossing -180
00794         _delta_pitch   = vp.getPitch() - _start_viewpoint.getPitch();
00795         _delta_range   = vp.getRange() - _start_viewpoint.getRange();
00796         _delta_focal_point = vp.getFocalPoint() - _start_viewpoint.getFocalPoint(); // TODO: adjust for lon=180 crossing
00797 
00798         while( _delta_heading > 180.0 ) _delta_heading -= 360.0;
00799         while( _delta_heading < -180.0 ) _delta_heading += 360.0;
00800 
00801         // adjust for geocentric date-line crossing
00802         if ( _is_geocentric )
00803         {
00804             while( _delta_focal_point.x() > 180.0 ) _delta_focal_point.x() -= 360.0;
00805             while( _delta_focal_point.x() < -180.0 ) _delta_focal_point.x() += 360.0;
00806         }
00807 
00808         // calculate an acceleration factor based on the Z differential
00809         double h0 = _start_viewpoint.getRange() * sin( osg::DegreesToRadians(-_start_viewpoint.getPitch()) );
00810         double h1 = vp.getRange() * sin( osg::DegreesToRadians( -vp.getPitch() ) );
00811         double dh = (h1 - h0);
00812 
00813         // calculate the total distance the focal point will travel and derive an arc height:
00814         double de;
00815         if ( _is_geocentric && (vp.getSRS() == 0L || vp.getSRS()->isGeographic()) )
00816         {
00817             osg::Vec3d startFP = _start_viewpoint.getFocalPoint();
00818             double x0,y0,z0, x1,y1,z1;
00819             _cached_srs->getEllipsoid()->convertLatLongHeightToXYZ(
00820                 osg::DegreesToRadians( _start_viewpoint.y() ), osg::DegreesToRadians( _start_viewpoint.x() ), 0.0, x0, y0, z0 );
00821             _cached_srs->getEllipsoid()->convertLatLongHeightToXYZ(
00822                 osg::DegreesToRadians( vp.y() ), osg::DegreesToRadians( vp.x() ), 0.0, x1, y1, z1 );
00823             de = (osg::Vec3d(x0,y0,z0) - osg::Vec3d(x1,y1,z1)).length();
00824         }
00825         else
00826         {
00827             de = _delta_focal_point.length();
00828         }
00829 
00830         _arc_height = 0.0;
00831         if ( _settings->getArcViewpointTransitions() )
00832         {         
00833             _arc_height = osg::maximum( de - fabs(dh), 0.0 );
00834         }
00835 
00836         // calculate acceleration coefficients
00837         if ( _arc_height > 0.0 )
00838         {
00839             // if we're arcing, we need seperate coefficients for the up and down stages
00840             double h_apex = 2.0*(h0+h1) + _arc_height;
00841             double dh2_up = fabs(h_apex - h0)/100000.0;
00842             _set_viewpoint_accel = log10( dh2_up );
00843             double dh2_down = fabs(h_apex - h1)/100000.0;
00844             _set_viewpoint_accel_2 = -log10( dh2_down );
00845         }
00846         else
00847         {
00848             // on arc => simple unidirectional acceleration:
00849             double dh2 = (h1 - h0)/100000.0;
00850             _set_viewpoint_accel = fabs(dh2) <= 1.0? 0.0 : dh2 > 0.0? log10( dh2 ) : -log10( -dh2 );
00851             if ( fabs( _set_viewpoint_accel ) < 1.0 ) _set_viewpoint_accel = 0.0;
00852         }
00853         
00854         if ( _settings->getAutoViewpointDurationEnabled() )
00855         {
00856             double maxDistance = _cached_srs->getEllipsoid()->getRadiusEquator();
00857             double ratio = osg::clampBetween( de/maxDistance, 0.0, 1.0 );
00858             ratio = accelerationInterp( ratio, -4.5 );
00859             double minDur, maxDur;
00860             _settings->getAutoViewpointDurationLimits( minDur, maxDur );
00861             duration_s = minDur + ratio*(maxDur-minDur);
00862         }
00863         
00864         // don't use _time_s_now; that's the time of the last event
00865         _time_s_set_viewpoint = osg::Timer::instance()->time_s();
00866         _set_viewpoint_duration_s = duration_s;
00867 
00868 //        OE_NOTICE
00872 //            << ", h0=" << h0
00873 //            << ", h1=" << h0
00874 //            << ", dh=" << dh
00875 //            //<< ", h_delta=" << h_delta
00876 //            << ", accel = " << _set_viewpoint_accel
00877 //            << ", archeight = " << _arc_height
00879 //            << std::endl;
00880 
00881         _setting_viewpoint = true;
00882         
00883         _thrown = false;
00884         _task->_type = TASK_NONE;
00885 
00886         recalculateCenter( getMyCoordinateFrame(_center) );
00887         //recalculateCenter( getCoordinateFrame(_center) );
00888     }
00889     else
00890     {
00891         osg::Vec3d new_center = vp.getFocalPoint();
00892 
00893         // start by transforming the requested focal point into world coordinates:
00894         if ( getSRS() )
00895         {
00896             // resolve the VP's srs. If the VP's SRS is not specified, assume that it
00897             // is either lat/long (if the map is geocentric) or X/Y (otherwise).
00898             osg::ref_ptr<const SpatialReference> vp_srs = vp.getSRS()? vp.getSRS() :
00899                 _is_geocentric? getSRS()->getGeographicSRS() :
00900                 getSRS();
00901 
00902             if ( !getSRS()->isEquivalentTo( vp_srs.get() ) )
00903             {
00904                 osg::Vec3d local = new_center;
00905                 // reproject the focal point if necessary:
00906                 vp_srs->transform2D( new_center.x(), new_center.y(), getSRS(), local.x(), local.y() );
00907                 new_center = local;
00908             }
00909 
00910             // convert to geocentric coords if necessary:
00911             if ( _is_geocentric )
00912             {
00913                 osg::Vec3d geocentric;
00914 
00915                 getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(
00916                     osg::DegreesToRadians( new_center.y() ),
00917                     osg::DegreesToRadians( new_center.x() ),
00918                     new_center.z(),
00919                     geocentric.x(), geocentric.y(), geocentric.z() );
00920 
00921                 new_center = geocentric;            
00922             }
00923         }
00924 
00925         // now calculate the new rotation matrix based on the angles:
00926 
00927 
00928         double new_pitch = osg::DegreesToRadians(
00929             osg::clampBetween( vp.getPitch(), _settings->getMinPitch(), _settings->getMaxPitch() ) );
00930 
00931         double new_azim = normalizeAzimRad( osg::DegreesToRadians( vp.getHeading() ) );
00932 
00933         _center = new_center;
00934                 setDistance( vp.getRange() );
00935         
00936         osg::CoordinateFrame local_frame = getMyCoordinateFrame( new_center ); //getCoordinateFrame( new_center );
00937         _previousUp = getUpVector( local_frame );
00938 
00939         _centerRotation = getRotation( new_center ).getRotate().inverse();
00940 
00941                 osg::Quat azim_q( new_azim, osg::Vec3d(0,0,1) );
00942         osg::Quat pitch_q( -new_pitch -osg::PI_2, osg::Vec3d(1,0,0) );
00943 
00944                 osg::Matrix new_rot = osg::Matrixd( azim_q * pitch_q );
00945 
00946                 _rotation = osg::Matrixd::inverse(new_rot).getRotate();
00947 
00948                 //OE_NOTICE << "Pitch old=" << _local_pitch << " new=" << new_pitch << std::endl;
00949                 //OE_NOTICE << "Azim old=" << _local_azim << " new=" << new_azim << std::endl;
00950 
00951         _local_pitch = new_pitch;
00952         _local_azim  = new_azim;
00953 
00954         // re-intersect the terrain to get a new correct center point, but only if this is
00955         // NOT a viewpoint transition update. (disabled check for now)
00956         //if ( !_setting_viewpoint )
00957         recalculateCenter( local_frame );
00958     }
00959 }
00960 
00961 void
00962 EarthManipulator::updateSetViewpoint()
00963 {
00964     double t = ( _time_s_now - _time_s_set_viewpoint ) / _set_viewpoint_duration_s;
00965     double tp = t;
00966 
00967     if ( t >= 1.0 )
00968     {
00969         t = tp = 1.0;
00970         _setting_viewpoint = false;
00971     }
00972     else if ( _arc_height > 0.0 )
00973     {
00974         if ( tp <= 0.5 )
00975         {
00976             double t2 = 2.0*tp;
00977             t2 = accelerationInterp( t2, _set_viewpoint_accel );
00978             tp = 0.5*t2;
00979         }
00980         else
00981         {
00982             double t2 = 2.0*(tp-0.5);
00983             t2 = accelerationInterp( t2, _set_viewpoint_accel_2 );
00984             tp = 0.5+(0.5*t2);
00985         }
00986 
00987         // the more smoothsteps you do, the more pronounced the fade-in/out effect        
00988         tp = smoothStepInterp( tp );
00989         tp = smoothStepInterp( tp );
00990     }
00991     else if ( t > 0.0 )
00992     {
00993         tp = accelerationInterp( tp, _set_viewpoint_accel );
00994         tp = smoothStepInterp( tp );
00995     }
00996 
00997     Viewpoint new_vp(
00998         _start_viewpoint.getFocalPoint() + _delta_focal_point * tp,
00999         _start_viewpoint.getHeading() + _delta_heading * tp,
01000         _start_viewpoint.getPitch() + _delta_pitch * tp,
01001         _start_viewpoint.getRange() + _delta_range * tp + (sin(osg::PI*tp)*_arc_height),
01002         _start_viewpoint.getSRS() );
01003 
01004 #if 0
01005     OE_INFO
01006         << "t=" << t 
01007         << ", tp=" << tp
01008         << ", tsv=" << _time_s_set_viewpoint
01009         << ", now=" << _time_s_now
01010         << ", accel=" << _set_viewpoint_accel
01011         << ", accel2=" << _set_viewpoint_accel_2
01012         << std::endl;
01013 #endif
01014 
01015     setViewpoint( new_vp );
01016 }
01017 
01018 
01019 Viewpoint
01020 EarthManipulator::getViewpoint() const
01021 {
01022     osg::Vec3d focal_point = _center;
01023 
01024     if ( getSRS() && _is_geocentric )
01025     {
01026         // convert geocentric to lat/long:
01027         getSRS()->getEllipsoid()->convertXYZToLatLongHeight(
01028             _center.x(), _center.y(), _center.z(),
01029             focal_point.y(), focal_point.x(), focal_point.z() );
01030 
01031         focal_point.x() = osg::RadiansToDegrees( focal_point.x() );
01032         focal_point.y() = osg::RadiansToDegrees( focal_point.y() );
01033     }
01034 
01035     return Viewpoint(
01036         focal_point,
01037         osg::RadiansToDegrees( _local_azim ),
01038         osg::RadiansToDegrees( _local_pitch ),
01039         _distance,
01040         getSRS() );
01041 }
01042 
01043 
01044 void
01045 EarthManipulator::setTetherNode( osg::Node* node )
01046 {
01047         if (_tether_node != node)
01048         {
01049                 _offset_x = 0.0;
01050                 _offset_y = 0.0;
01051         }
01052     _tether_node = node;
01053 }
01054 
01055 osg::Node*
01056 EarthManipulator::getTetherNode() const
01057 {
01058     return _tether_node.get();
01059 }
01060 
01061 
01062 bool
01063 EarthManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const
01064 {
01065     osg::ref_ptr<osg::Node> safeNode = _node.get();
01066     if ( safeNode.valid() )
01067     {
01068         osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi = new osgUtil::LineSegmentIntersector(start,end);
01069 
01070         osgUtil::IntersectionVisitor iv(lsi.get());
01071         iv.setTraversalMask(_intersectTraversalMask);
01072 
01073         safeNode->accept(iv);
01074 
01075         if (lsi->containsIntersections())
01076         {
01077             intersection = lsi->getIntersections().begin()->getWorldIntersectPoint();
01078             return true;
01079         }
01080     }
01081     return false;
01082 }
01083 
01084 void
01085 EarthManipulator::home(const osgGA::GUIEventAdapter& ,osgGA::GUIActionAdapter& us)
01086 {
01087     handleAction( ACTION_HOME, 0, 0, 0 );
01088     //if (getAutoComputeHomePosition()) computeHomePosition();
01089     //setByLookAt(_homeEye, _homeCenter, _homeUp);
01090     us.requestRedraw();
01091 }
01092 
01093 void
01094 EarthManipulator::computeHomePosition()
01095 {    
01096     if( getNode() )
01097     {
01098         const osg::BoundingSphere& boundingSphere = getNode()->getBound();
01099 
01100         osg::Vec3d eye =
01101             boundingSphere._center +
01102             osg::Vec3( 0.0, -3.5f * boundingSphere._radius, boundingSphere._radius * 0.0001 );
01103 
01104         setHomePosition(
01105             eye,
01106             boundingSphere._center,
01107             osg::Vec3d( 0, 0, 1 ),
01108             _autoComputeHomePosition );
01109     }
01110 }
01111 
01112 void
01113 EarthManipulator::init(const osgGA::GUIEventAdapter&, osgGA::GUIActionAdapter& )
01114 {
01115     flushMouseEventStack();
01116 }
01117 
01118 
01119 void
01120 EarthManipulator::getUsage(osg::ApplicationUsage& usage) const
01121 {
01122 }
01123 
01124 void
01125 EarthManipulator::resetMouse( osgGA::GUIActionAdapter& aa )
01126 {
01127     flushMouseEventStack();
01128     aa.requestContinuousUpdate( false );
01129     _thrown = false;
01130     _continuous = false;
01131     _single_axis_x = 1.0;
01132     _single_axis_y = 1.0;
01133     _lastPointOnEarth.set(0.0, 0.0, 0.0);
01134 }
01135 
01136 // this method will automatically install or uninstall the camera post-update callback 
01137 // depending on whether there's a tether node.
01138 //
01139 // Camera updates get called AFTER the scene gets its update traversal. So, if you have
01140 // tethering enabled (or some other feature that tracks scene graph nodes), this will
01141 // update the camera after the scene graph. This is important in order to maintain
01142 // frame coherency and prevent "jitter".
01143 //
01144 // The reason we install/uninstall instead of just leaving it there is so we can
01145 // support OSG's "ON_DEMAND" frame scheme, which disables itself is there are any
01146 // update callbacks in the scene graph.
01147 void
01148 EarthManipulator::updateCamera( osg::Camera* eventCamera )
01149 {
01150     // check to see if the camera has changed, and update the callback if necessary
01151     if ( _viewCamera.get() != eventCamera )
01152     {
01153         if ( _cameraUpdateCB.valid() )
01154             _viewCamera->removeUpdateCallback( _cameraUpdateCB.get() );
01155 
01156         _viewCamera = eventCamera;
01157         if ( _cameraUpdateCB.valid() )
01158             _viewCamera->addUpdateCallback( _cameraUpdateCB.get() );
01159     }
01160 
01161     // check to see if we need to install a new camera callback:
01162     if ( _tether_node.valid() && !_cameraUpdateCB.valid() )
01163     {
01164         _cameraUpdateCB = new CameraPostUpdateCallback(this);
01165         _viewCamera->addUpdateCallback( _cameraUpdateCB.get() );
01166     }
01167     else if ( !_tether_node.valid() && _cameraUpdateCB.valid() )
01168     {
01169         _viewCamera->removeUpdateCallback( _cameraUpdateCB.get() );
01170         _cameraUpdateCB = 0L;
01171     }
01172 }
01173 
01174 bool
01175 EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
01176 {
01177     bool handled = false;
01178     
01179     // first order of business: make sure the CSN is established.
01180     if ( !established() )
01181         return false;
01182 
01183     // make sure the camera callback is up to date:
01184     updateCamera( aa.asView()->getCamera() );
01185 
01186     if ( ea.getEventType() == osgGA::GUIEventAdapter::FRAME )
01187     {
01188         _time_s_last_frame = _time_s_now;
01189         _time_s_now = osg::Timer::instance()->time_s();
01190         _delta_t = _time_s_now - _time_s_last_frame;
01191         // this factor adjusts for the variation of frame rate relative to 60fps
01192         _t_factor = _delta_t / 0.01666666666;
01193 
01194         //OE_NOTICE
01195         //    << "center=(" << _center.x() << "," << _center.y() << "," << _center.z() << ")"
01196         //    << ", dist=" << _distance
01197         //    << ", p=" << _local_pitch
01198         //    << ", h=" << _local_azim
01199         //    << std::endl;
01200 
01201         if ( _has_pending_viewpoint && _node.valid() )
01202         {
01203             _has_pending_viewpoint = false;
01204             setViewpoint( _pending_viewpoint, _pending_viewpoint_duration_s );
01205             aa.requestRedraw();
01206         }
01207 
01208         if ( _setting_viewpoint )
01209         {
01210             updateSetViewpoint();
01211             aa.requestRedraw();
01212         }
01213 
01214         if ( _thrown || _continuous )
01215         {
01216             handleContinuousAction( _last_action, aa.asView() );
01217             aa.requestRedraw();
01218         }
01219 
01220         if ( !_continuous )
01221         {
01222             _continuous_dx = 0.0;
01223             _continuous_dy = 0.0;
01224         }
01225         
01226         if ( _task.valid() )
01227         {
01228             if ( serviceTask() )
01229                 aa.requestRedraw();
01230         }
01231 
01232         _after_first_frame = true;
01233 
01234         return false;
01235     }
01236 
01237     // the camera manipulator runs last after any other event handlers. So bail out
01238     // if the incoming event has already been handled by another handler.
01239     if ( ea.getHandled() )
01240     {
01241         return false;
01242     }
01243    
01244     // form the current Action based on the event type:
01245     Action action = ACTION_NULL;
01246 
01247     switch( ea.getEventType() )
01248     {
01249         case osgGA::GUIEventAdapter::PUSH:
01250             resetMouse( aa );
01251             addMouseEvent( ea );
01252             _mouse_down_event = &ea;
01253             aa.requestRedraw();
01254             handled = true;
01255             break;       
01256         
01257         case osgGA::GUIEventAdapter::RELEASE:
01258 
01259             if ( _continuous )
01260             {
01261                 // bail out of continuous mode if necessary:
01262                 _continuous = false;
01263             }
01264             else
01265             {
01266                 // check for a mouse-throw continuation:
01267                 if ( _settings->getThrowingEnabled() && isMouseMoving() )
01268                 {
01269                     action = _last_action;
01270                     if( handleMouseAction( action, aa.asView() ) )
01271                     {
01272                         aa.requestRedraw();
01273                         aa.requestContinuousUpdate( true );
01274                         _thrown = true;
01275                     }
01276                 }
01277                 else if ( isMouseClick( &ea ) )
01278                 {
01279                     addMouseEvent( ea );
01280                     if ( _mouse_down_event )
01281                     {
01282                         action = _settings->getAction( EVENT_MOUSE_CLICK, _mouse_down_event->getButtonMask(), _mouse_down_event->getModKeyMask() );
01283                         if ( handlePointAction( action, ea.getX(), ea.getY(), aa.asView() ))
01284                             aa.requestRedraw();                
01285                     }
01286                     resetMouse( aa );
01287                 }
01288                 else
01289                 {
01290                     resetMouse( aa );
01291                     addMouseEvent( ea );
01292                 }
01293             }
01294             handled = true;
01295             break;
01296             
01297         case osgGA::GUIEventAdapter::DOUBLECLICK:
01298             // bail out of continuous mode if necessary:
01299             _continuous = false;
01300 
01301             addMouseEvent( ea );
01302                         if (_mouse_down_event)
01303                         {
01304                                 action = _settings->getAction( EVENT_MOUSE_DOUBLE_CLICK, _mouse_down_event->getButtonMask(), _mouse_down_event->getModKeyMask() );
01305                                 if ( handlePointAction( action, ea.getX(), ea.getY(), aa.asView() ) )
01306                                         aa.requestRedraw();
01307                                 resetMouse( aa );
01308                                 handled = true;
01309                         }
01310             break;
01311 
01312         case osgGA::GUIEventAdapter::MOVE: // MOVE not currently bindable
01313             //NOP
01314             break;
01315 
01316         case osgGA::GUIEventAdapter::DRAG:
01317             {
01318                 action = _settings->getAction( ea.getEventType(), ea.getButtonMask(), ea.getModKeyMask() );
01319                 addMouseEvent( ea );
01320                 _continuous = action.getBoolOption(OPTION_CONTINUOUS, false);
01321                 if ( handleMouseAction( action, aa.asView() ) )
01322                     aa.requestRedraw();
01323                 aa.requestContinuousUpdate(false);
01324                 _thrown = false;
01325                 handled = true;
01326             }
01327             break;
01328 
01329         case osgGA::GUIEventAdapter::KEYDOWN:
01330             if ( ea.getKey() < osgGA::GUIEventAdapter::KEY_Shift_L )
01331             {
01332                 resetMouse( aa );
01333                 action = _settings->getAction( ea.getEventType(), ea.getKey(), ea.getModKeyMask() );
01334                 if ( handleKeyboardAction( action ) )
01335                     aa.requestRedraw();
01336                 handled = true;
01337             }
01338             break;
01339             
01340         case osgGA::GUIEventAdapter::KEYUP:
01341             resetMouse( aa );
01342             _task->_type = TASK_NONE;
01343             handled = true;
01344             break;
01345 
01346         case osgGA::GUIEventAdapter::SCROLL:
01347             resetMouse( aa );
01348             addMouseEvent( ea );
01349             action = _settings->getAction( ea.getEventType(), ea.getScrollingMotion(), ea.getModKeyMask() );
01350             if ( handleScrollAction( action, 0.2 ) )
01351                 aa.requestRedraw();
01352             handled = true;
01353             break;
01354     }
01355 
01356     if ( handled && action._type != ACTION_NULL )
01357         _last_action = action;
01358 
01359     return handled;
01360 }
01361 
01362 void
01363 EarthManipulator::postUpdate()
01364 {
01365     updateTether();
01366 }
01367 
01368 void
01369 EarthManipulator::updateTether()
01370 {
01371     // capture a temporary ref since _tether_node is just an observer:
01372     osg::ref_ptr<osg::Node> temp = _tether_node.get();
01373     if ( temp.valid() )
01374     {
01375 
01376                 osg::NodePathList nodePaths = temp->getParentalNodePaths();
01377         if ( nodePaths.empty() )
01378             return;
01379         osg::NodePath path = nodePaths[0];
01380 
01381         osg::Matrixd localToWorld = osg::computeLocalToWorld( path );
01382         _center = osg::Vec3d(0,0,0) * localToWorld;
01383 
01384         // if the tether node is a MT, we are set. If it's not, we need to get the
01385         // local bound and add its translation to the localToWorld. We cannot just use
01386         // the bounds directly because they are single precision (unless you built OSG
01387         // with double-precision bounding spheres, which you probably did not :)
01388         if ( !dynamic_cast<osg::MatrixTransform*>( temp.get() ) )
01389         {
01390             const osg::BoundingSphere& bs = temp->getBound();
01391             _center += bs.center();
01392         }
01393 
01394         //OE_INFO
01395         //    << std::fixed << std::setprecision(3)
01396         //    << "Tether center: (" << _center.x() << "," << _center.y() << "," << _center.z()
01397         //    << "); bbox center: (" << bs.center().x() << "," << bs.center().y() << "," << bs.center().z() << ")"
01398         //    << std::endl;
01399 
01400                 osg::CoordinateFrame local_frame = getMyCoordinateFrame( _center ); //getCoordinateFrame( _center );
01401             _previousUp = getUpVector( local_frame );
01402 
01403 //                      osg::Matrixd localToWorld = osg::computeLocalToWorld( path );
01404                 double sx = 1.0/sqrt(localToWorld(0,0)*localToWorld(0,0) + localToWorld(1,0)*localToWorld(1,0) + localToWorld(2,0)*localToWorld(2,0));
01405                 double sy = 1.0/sqrt(localToWorld(0,1)*localToWorld(0,1) + localToWorld(1,1)*localToWorld(1,1) + localToWorld(2,1)*localToWorld(2,1));
01406                 double sz = 1.0/sqrt(localToWorld(0,2)*localToWorld(0,2) + localToWorld(1,2)*localToWorld(1,2) + localToWorld(2,2)*localToWorld(2,2));
01407                 localToWorld = localToWorld*osg::Matrixd::scale(sx,sy,sz);
01408 
01409         // didn't we just call this a few lines ago?
01410             //osg::CoordinateFrame coordinateFrame = getMyCoordinateFrame( _center ); //getCoordinateFrame(_center);
01411 
01412                 //Just track the center
01413                 if (_settings->getTetherMode() == TETHER_CENTER)
01414                 {
01415                         _centerRotation = local_frame.getRotate(); //coordinateFrame.getRotate();
01416                 }
01417                 //Track all rotations
01418                 else if (_settings->getTetherMode() == TETHER_CENTER_AND_ROTATION)
01419                 {
01420                   _centerRotation = localToWorld.getRotate();
01421                 }
01422                 else if (_settings->getTetherMode() == TETHER_CENTER_AND_HEADING)
01423                 {
01424                         //Track just the heading
01425                         osg::Matrixd localToFrame(localToWorld*osg::Matrixd::inverse( local_frame )); //coordinateFrame));
01426                         double azim = atan2(-localToFrame(0,1),localToFrame(0,0));
01427                         osg::Quat nodeRotationRelToFrame, rotationOfFrame;
01428                         nodeRotationRelToFrame.makeRotate(-azim,0.0,0.0,1.0);
01429                         rotationOfFrame = local_frame.getRotate(); //coordinateFrame.getRotate();
01430                         _centerRotation = nodeRotationRelToFrame*rotationOfFrame;
01431                 }
01432     }
01433 }
01434 
01435 bool
01436 EarthManipulator::serviceTask()
01437 {
01438     bool result;
01439 
01440     if ( _task.valid() && _task->_type != TASK_NONE )
01441     {
01442         switch( _task->_type )
01443         {
01444             case TASK_PAN:
01445                 pan( _delta_t * _task->_dx, _delta_t * _task->_dy );
01446                 break;
01447             case TASK_ROTATE:
01448                 rotate( _delta_t * _task->_dx, _delta_t * _task->_dy );
01449                 break;
01450             case TASK_ZOOM:
01451                 zoom( _delta_t * _task->_dx, _delta_t * _task->_dy );
01452                 break;
01453             default: break;
01454         }
01455 
01456         _task->_duration_s -= _delta_t;
01457         if ( _task->_duration_s <= 0.0 )
01458             _task->_type = TASK_NONE;
01459 
01460         result = true;
01461     }
01462     else
01463     {
01464         result = false;
01465     }
01466 
01467     //_time_last_frame = now;
01468     return result;
01469 }
01470 
01471 bool
01472 EarthManipulator::isMouseMoving()
01473 {
01474     if (_ga_t0.get()==NULL || _ga_t1.get()==NULL) return false;
01475 
01476     static const float velocity = 0.1f;
01477 
01478     float dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized();
01479     float dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized();
01480     float len = sqrtf(dx*dx+dy*dy);
01481     //float dt = _ga_t0->getTime()-_ga_t1->getTime();
01482 
01483     return len > _delta_t * velocity;
01484 }
01485 
01486 bool
01487 EarthManipulator::isMouseClick( const osgGA::GUIEventAdapter* mouse_up_event ) const
01488 {
01489     if ( mouse_up_event == NULL || _mouse_down_event == NULL ) return false;
01490 
01491     static const float velocity = 0.1f;
01492 
01493     float dx = mouse_up_event->getXnormalized() - _mouse_down_event->getXnormalized();
01494     float dy = mouse_up_event->getYnormalized() - _mouse_down_event->getYnormalized();
01495     float len = sqrtf( dx*dx + dy*dy );
01496     float dt = mouse_up_event->getTime( ) - _mouse_down_event->getTime();
01497 
01498     return len < dt * velocity;
01499 }
01500 
01501 void
01502 EarthManipulator::flushMouseEventStack()
01503 {
01504     _ga_t1 = NULL;
01505     _ga_t0 = NULL;
01506 }
01507 
01508 
01509 void
01510 EarthManipulator::addMouseEvent(const osgGA::GUIEventAdapter& ea)
01511 {
01512     _ga_t1 = _ga_t0;
01513     _ga_t0 = &ea;
01514 }
01515 
01516 void
01517 EarthManipulator::setByMatrix(const osg::Matrixd& matrix)
01518 {
01519     osg::Vec3d lookVector(- matrix(2,0),-matrix(2,1),-matrix(2,2));
01520     osg::Vec3d eye(matrix(3,0),matrix(3,1),matrix(3,2));
01521 
01522     _centerRotation = makeCenterRotation(_center);
01523 
01524     osg::ref_ptr<osg::Node> safeNode = _node.get();
01525 
01526     if ( !safeNode.valid() )
01527     {
01528         _center = eye + lookVector;
01529         setDistance( lookVector.length() );
01530         _rotation = matrix.getRotate().inverse() * _centerRotation.inverse();   
01531         return;
01532     }
01533 
01534     // need to reintersect with the terrain
01535     const osg::BoundingSphere& bs = safeNode->getBound();
01536     float distance = (eye-bs.center()).length() + safeNode->getBound().radius();
01537     osg::Vec3d start_segment = eye;
01538     osg::Vec3d end_segment = eye + lookVector*distance;
01539     
01540     osg::Vec3d ip;
01541     bool hitFound = false;
01542     if (intersect(start_segment, end_segment, ip))
01543     {
01544         _center = ip;
01545         _centerRotation = makeCenterRotation(_center);
01546         setDistance( (eye-ip).length());
01547 
01548         osg::Matrixd rotation_matrix = osg::Matrixd::translate(0.0,0.0,-_distance)*
01549                                        matrix*
01550                                        osg::Matrixd::translate(-_center);
01551         _rotation = rotation_matrix.getRotate() * _centerRotation.inverse();
01552         hitFound = true;
01553     }
01554 
01555     if (!hitFound)
01556     {
01557         osg::CoordinateFrame eyePointCoordFrame = getMyCoordinateFrame( eye ); //getCoordinateFrame( eye );
01558 
01559         if (intersect(eye+getUpVector(eyePointCoordFrame)*distance,
01560                       eye-getUpVector(eyePointCoordFrame)*distance,
01561                       ip))
01562         {
01563             _center = ip;
01564             _centerRotation = makeCenterRotation(_center);
01565             setDistance((eye-ip).length());
01566                         _rotation.set(0,0,0,1);
01567             hitFound = true;
01568         }
01569     }
01570 
01571     osg::CoordinateFrame coordinateFrame = getMyCoordinateFrame( _center ); //getCoordinateFrame( _center );
01572     _previousUp = getUpVector(coordinateFrame);
01573 
01574     recalculateRoll();
01575     recalculateLocalPitchAndAzimuth();
01576 }
01577 
01578 osg::Matrixd
01579 EarthManipulator::getMatrix() const
01580 {
01581     return osg::Matrixd::translate(-_offset_x,-_offset_y,_distance)*
01582                    osg::Matrixd::rotate(_rotation)*
01583                    osg::Matrixd::rotate(_centerRotation)*
01584                    osg::Matrixd::translate(_center);
01585 }
01586 
01587 osg::Matrixd
01588 EarthManipulator::getInverseMatrix() const
01589 {
01590     return osg::Matrixd::translate(-_center)*
01591                    osg::Matrixd::rotate(_centerRotation.inverse() ) *
01592                    osg::Matrixd::rotate(_rotation.inverse())*
01593                    osg::Matrixd::translate(_offset_x,_offset_y,-_distance);
01594 }
01595 
01596 void
01597 EarthManipulator::setByLookAt(const osg::Vec3d& eye,const osg::Vec3d& center,const osg::Vec3d& up)
01598 {
01599     osg::ref_ptr<osg::Node> safeNode = _node.get();
01600 
01601     if ( !safeNode.valid() ) return;
01602 
01603     // compute rotation matrix
01604     osg::Vec3d lv(center-eye);
01605     setDistance( lv.length() );
01606     _center = center;
01607 
01608     if (_node.valid())
01609     {
01610         bool hitFound = false;
01611 
01612         double distance = lv.length();
01613         double maxDistance = distance+2*(eye-safeNode->getBound().center()).length();
01614         osg::Vec3d farPosition = eye+lv*(maxDistance/distance);
01615         osg::Vec3d endPoint = center;
01616         for(int i=0;
01617             !hitFound && i<2;
01618             ++i, endPoint = farPosition)
01619         {
01620             // compute the intersection with the scene.s
01621             
01622             osg::Vec3d ip;
01623             if (intersect(eye, endPoint, ip))
01624             {
01625                 _center = ip;
01626                 setDistance( (ip-eye).length() );
01627                 hitFound = true;
01628             }
01629         }
01630     }
01631 
01632     // note LookAt = inv(CF)*inv(RM)*inv(T) which is equivalent to:
01633     // inv(R) = CF*LookAt.
01634 
01635     osg::Matrixd rotation_matrix = osg::Matrixd::lookAt(eye,center,up);
01636 
01637     _centerRotation = getRotation( _center ).getRotate().inverse();
01638         _rotation = rotation_matrix.getRotate().inverse() * _centerRotation.inverse();  
01639         
01640 
01641     osg::CoordinateFrame coordinateFrame = getMyCoordinateFrame( _center ); //getCoordinateFrame(_center);
01642     _previousUp = getUpVector(coordinateFrame);
01643 
01644     recalculateRoll();
01645     recalculateLocalPitchAndAzimuth();
01646 }
01647 
01648 
01649 void
01650 EarthManipulator::recalculateCenter( const osg::CoordinateFrame& coordinateFrame )
01651 {
01652     osg::ref_ptr<osg::Node> safeNode = _node.get();
01653     if ( safeNode.valid() )
01654     {
01655         bool hitFound = false;
01656 
01657         // need to reintersect with the terrain
01658         double distance = safeNode->getBound().radius()*0.25f;
01659 
01660         //OE_NOTICE
01661         //    << std::fixed
01662         //    << "ISECT: center=(" << _center.x() << "," << _center.y() << "," << _center.y() << ")"
01663         //    << ", distnace=" << distance
01664         //    << std::endl;
01665 
01666         //osg::Vec3d ev = getUpVector(coordinateFrame);
01667         //osg::Vec3d ip;
01668         //if ( intersect( _center -ev * distance, _center + ev*distance, ip ) )
01669         //{
01670         //    _center = ip;
01671         //    hitFound = true;
01672         //}
01673 
01674         osg::Vec3d ip1;
01675         osg::Vec3d ip2;
01676         // extend coordonate to fall on the edge of the boundingbox see http://www.osgearth.org/ticket/113
01677         bool hit_ip1 = intersect(_center - getUpVector(coordinateFrame) * distance * 0.1, _center + getUpVector(coordinateFrame) * distance, ip1);
01678         bool hit_ip2 = intersect(_center + getUpVector(coordinateFrame) * distance * 0.1, _center - getUpVector(coordinateFrame) * distance, ip2);
01679         if (hit_ip1)
01680         {
01681             if (hit_ip2)
01682             {
01683                 _center = (_center-ip1).length2() < (_center-ip2).length2() ? ip1 : ip2;
01684                 hitFound = true;
01685             }
01686             else
01687             {
01688                 _center = ip1;
01689                 hitFound = true;
01690             }
01691         }
01692         else if (hit_ip2)
01693         {
01694             _center = ip2;
01695             hitFound = true;
01696         }
01697 
01698         if (!hitFound)
01699         {
01700             // ??
01701             //OE_DEBUG<<"EarthManipulator unable to intersect with terrain."<<std::endl;
01702         }
01703     }
01704 }
01705 
01706 
01707 void
01708 EarthManipulator::pan( double dx, double dy )
01709 {
01710         //OE_NOTICE << "pan " << dx << "," << dy <<  std::endl;
01711         if (!_tether_node.valid())
01712         {
01713                 double scale = -0.3f*_distance;
01714                 //double old_azim = _local_azim;
01715                 double old_azim = getAzimuth();
01716 
01717                 osg::Matrixd rotation_matrix;// = getMatrix();
01718                 rotation_matrix.makeRotate( _rotation * _centerRotation  );
01719 
01720                 // compute look vector.
01721                 osg::Vec3d lookVector = -getUpVector(rotation_matrix);
01722                 osg::Vec3d sideVector = getSideVector(rotation_matrix);
01723                 osg::Vec3d upVector = getFrontVector(rotation_matrix);
01724 
01725                 osg::Vec3d localUp = _previousUp;
01726 
01727                 osg::Vec3d forwardVector =localUp^sideVector;
01728                 sideVector = forwardVector^localUp;
01729 
01730                 forwardVector.normalize();
01731                 sideVector.normalize();
01732 
01733                 osg::Vec3d dv = forwardVector * (dy*scale) + sideVector * (dx*scale);
01734 
01735                 // save the previous CF so we can do azimuth locking:
01736                 osg::CoordinateFrame old_frame = getMyCoordinateFrame( _center ); //getCoordinateFrame( _center );
01737 
01738                 _center += dv;
01739 
01740                 // need to recompute the intersection point along the look vector.
01741 
01742         osg::ref_ptr<osg::Node> safeNode = _node.get();
01743                 if (safeNode.valid())
01744                 {
01745                         // now reorientate the coordinate frame to the frame coords.
01746                         osg::CoordinateFrame coordinateFrame = old_frame; // getCoordinateFrame(_center);
01747 
01748                         recalculateCenter( coordinateFrame );
01749 
01750                         coordinateFrame = getMyCoordinateFrame( _center ); // getCoordinateFrame(_center);
01751                         osg::Vec3d new_localUp = getUpVector(coordinateFrame);
01752 
01753                         osg::Quat pan_rotation;
01754                         pan_rotation.makeRotate( localUp, new_localUp );
01755 
01756                         if ( !pan_rotation.zeroRotation() )
01757                         {
01758                                 _centerRotation = _centerRotation * pan_rotation;
01759                                 _previousUp = new_localUp;
01760                         }
01761                         else
01762                         {
01763                                 //OE_DEBUG<<"New up orientation nearly inline - no need to rotate"<<std::endl;
01764                         }
01765 
01766                         if ( _settings->getLockAzimuthWhilePanning() )
01767                         {
01768                                 double new_azim = getAzimuth();
01769                                 double delta_azim = new_azim - old_azim;
01770                                 //OE_NOTICE << "DeltaAzim" << delta_azim << std::endl;
01771 
01772                                 osg::Quat q;
01773                                 q.makeRotate( delta_azim, new_localUp );
01774                                 if ( !q.zeroRotation() )
01775                                 {
01776                                         _centerRotation = _centerRotation * q;
01777                                 }
01778                         }
01779                 }
01780 
01781                 recalculateLocalPitchAndAzimuth();
01782         }
01783         else
01784         {
01785                 double scale = _distance;
01786                 _offset_x += dx * scale;
01787                 _offset_y += dy * scale;
01788 
01789                 //Clamp values within range
01790                 if (_offset_x < -_settings->getMaxXOffset()) _offset_x = -_settings->getMaxXOffset();
01791                 if (_offset_y < -_settings->getMaxYOffset()) _offset_y = -_settings->getMaxYOffset();
01792                 if (_offset_x > _settings->getMaxXOffset()) _offset_x = _settings->getMaxXOffset();
01793                 if (_offset_y > _settings->getMaxYOffset()) _offset_y = _settings->getMaxYOffset();
01794         }
01795 }
01796 
01797 void
01798 EarthManipulator::rotate( double dx, double dy )
01799 {
01800         //OE_NOTICE << "rotate " << dx <<", " << dy << std::endl;
01801     // clamp the local pitch delta; never allow the pitch to hit -90.
01802     double minp = osg::DegreesToRadians( osg::clampAbove( _settings->getMinPitch(), -89.9 ) );
01803     double maxp = osg::DegreesToRadians( _settings->getMaxPitch() );
01804 
01805     //OE_NOTICE << LC 
01806     //    << "LocalPitch=" << osg::RadiansToDegrees(_local_pitch)
01807     //    << ", dy=" << osg::RadiansToDegrees(dy)
01808     //    << ", dy+lp=" << osg::RadiansToDegrees(_local_pitch+dy)
01809     //    << ", limits=" << osg::RadiansToDegrees(minp) << "," << osg::RadiansToDegrees(maxp)
01810     //    << std::endl;
01811 
01812     // clamp pitch range:
01813     if ( dy + _local_pitch > maxp || dy + _local_pitch < minp )
01814         dy = 0;
01815 
01816         osg::Matrix rotation_matrix;
01817         rotation_matrix.makeRotate(_rotation);
01818 
01819         osg::Vec3d lookVector = -getUpVector(rotation_matrix);
01820         osg::Vec3d sideVector = getSideVector(rotation_matrix);
01821         osg::Vec3d upVector = getFrontVector(rotation_matrix);
01822 
01823         osg::Vec3d localUp(0.0f,0.0f,1.0f);
01824 
01825         osg::Vec3d forwardVector = localUp^sideVector;
01826         sideVector = forwardVector^localUp;
01827 
01828         forwardVector.normalize();
01829         sideVector.normalize();
01830 
01831         osg::Quat rotate_elevation;
01832         rotate_elevation.makeRotate(dy,sideVector);
01833 
01834         osg::Quat rotate_azim;
01835         rotate_azim.makeRotate(-dx,localUp);
01836 
01837         _rotation = _rotation * rotate_elevation * rotate_azim;
01838         recalculateLocalPitchAndAzimuth();
01839 }
01840 
01841 void
01842 EarthManipulator::zoom( double dx, double dy )
01843 {
01844     double fd = 1000;
01845     double scale = 1.0f + dy;
01846 
01847     if ( fd * scale > _settings->getMinDistance() )
01848     {
01849         setDistance( _distance * scale );
01850     }
01851     else
01852     {
01853                 setDistance( _settings->getMinDistance() );
01854     }
01855 }
01856 
01857 bool
01858 EarthManipulator::screenToWorld(float x, float y, osg::View* theView, osg::Vec3d& out_coords ) const
01859 {
01860     osgViewer::View* view = dynamic_cast<osgViewer::View*>( theView );
01861     if ( !view || !view->getCamera() )
01862         return false;
01863 
01864     float local_x, local_y = 0.0;    
01865     const osg::Camera* camera = view->getCameraContainingPosition(x, y, local_x, local_y);
01866     if ( !camera )
01867         camera = view->getCamera();
01868 
01869     osgUtil::LineSegmentIntersector::CoordinateFrame cf = 
01870         camera->getViewport() ? osgUtil::Intersector::WINDOW : osgUtil::Intersector::PROJECTION;
01871 
01872     osg::ref_ptr< osgUtil::LineSegmentIntersector > picker = new osgUtil::LineSegmentIntersector(cf, local_x, local_y);
01873 
01874     osgUtil::IntersectionVisitor iv(picker.get());
01875     iv.setTraversalMask(_intersectTraversalMask);
01876 
01877     const_cast<osg::Camera*>(camera)->accept(iv);
01878 
01879     if ( picker->containsIntersections() )
01880     {
01881         osgUtil::LineSegmentIntersector::Intersections& results = picker->getIntersections();
01882         out_coords = results.begin()->getWorldIntersectPoint();
01883         return true;
01884     }
01885 
01886     return false;
01887 }
01888 
01889 void
01890 EarthManipulator::setDistance( double distance )
01891 {
01892         _distance = osg::clampBetween( distance, _settings->getMinDistance(), _settings->getMaxDistance() );
01893 }
01894 
01895 void
01896 EarthManipulator::dumpActionInfo( const EarthManipulator::Action& action, osg::NotifySeverity level ) const
01897 {
01898     osgEarth::notify(level) << "action: " << s_actionNames[action._type] << "; options: ";
01899     for( ActionOptions::const_iterator i = action._options.begin(); i != action._options.end(); ++i )
01900     {
01901         const ActionOption& option = *i;
01902         std::string val;
01903         if ( s_actionOptionTypes[option.option()] == 0 )
01904             val = option.boolValue() ? "true" : "false";
01905         else
01906             val = toString<double>(option.doubleValue());
01907 
01908         osgEarth::notify(level)
01909             << s_actionOptionNames[option.option()] << "=" << val << ", ";
01910     }
01911     osgEarth::notify(level) << std::endl;        
01912 }
01913 
01914 void
01915 EarthManipulator::handleMovementAction( const ActionType& type, double dx, double dy, osg::View* view )
01916 {
01917     switch( type )
01918     {
01919     case ACTION_PAN:
01920         pan( dx, dy );
01921         break;
01922 
01923     case ACTION_ROTATE:
01924         // in "single axis" mode, zero out one of the deltas.
01925         if ( _continuous && _settings->getSingleAxisRotation() )
01926         {
01927             if ( ::fabs(dx) > ::fabs(dy) )
01928                 dy = 0.0;
01929             else
01930                 dx = 0.0;
01931         }
01932         rotate( dx, dy );
01933         break;
01934 
01935     case ACTION_ZOOM:
01936         zoom( dx, dy );
01937         break;
01938 
01939     case ACTION_EARTH_DRAG:
01940         drag( dx, dy, view );
01941         break;
01942     }
01943 }
01944 
01945 bool
01946 EarthManipulator::handlePointAction( const Action& action, float mx, float my, osg::View* view )
01947 {
01948     //Exit early if the action is null
01949     if (action._type == ACTION_NULL)
01950         return true;
01951 
01952     osg::Vec3d point;
01953     if ( screenToWorld( mx, my, view, point ))
01954     {
01955         switch( action._type )
01956         {
01957             case ACTION_GOTO:
01958                 Viewpoint here = getViewpoint();
01959 
01960                 if ( getSRS() && _is_geocentric )
01961                 {
01962                     double lat_r, lon_r, h;
01963                     getSRS()->getEllipsoid()->convertXYZToLatLongHeight(
01964                         point.x(), point.y(), point.z(),
01965                         lat_r, lon_r, h );
01966                     point.set( osg::RadiansToDegrees(lon_r), osg::RadiansToDegrees(lat_r), h );
01967                 }
01968                 here.setFocalPoint( point );
01969 
01970                 double duration_s = action.getDoubleOption(OPTION_DURATION, 1.0);
01971                 double range_factor = action.getDoubleOption(OPTION_GOTO_RANGE_FACTOR, 1.0);
01972 
01973                 here.setRange( here.getRange() * range_factor );
01974 
01975                 setViewpoint( here, duration_s );
01976                 break;
01977         }
01978     }
01979     return true;
01980 }
01981 
01982 void
01983 EarthManipulator::handleContinuousAction( const Action& action, osg::View* view )
01984 {
01985     handleMovementAction( action._type, _continuous_dx * _t_factor, _continuous_dy * _t_factor, view );
01986 }
01987 
01988 void
01989 EarthManipulator::applyOptionsToDeltas( const Action& action, double& dx, double& dy )
01990 {
01991     dx *= action.getDoubleOption( OPTION_SCALE_X, 1.0 );
01992     dy *= action.getDoubleOption( OPTION_SCALE_Y, 1.0 );
01993 
01994     if ( action.getBoolOption( OPTION_SINGLE_AXIS, false ) == true )
01995     {
01996         if ( osg::absolute(dx) > osg::absolute(dy) )
01997             dy = 0.0;
01998         else
01999             dx = 0.0;
02000     }
02001 }
02002 
02003 bool
02004 EarthManipulator::handleMouseAction( const Action& action, osg::View* view )
02005 {
02006     // return if less then two events have been added.
02007     if (_ga_t0.get()==NULL || _ga_t1.get()==NULL) return false;
02008 
02009     //if ( osgEarth::getNotifyLevel() > osg::INFO )
02010     //    dumpActionInfo( action, osg::DEBUG_INFO );
02011 
02012     double dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized();
02013     double dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized();
02014 
02015     // return if there is no movement.
02016     if (dx==0 && dy==0) return false;
02017 
02018     // here we adjust for action scale, global sensitivy
02019     dx *= _settings->getMouseSensitivity();
02020     dy *= _settings->getMouseSensitivity();
02021 
02022     applyOptionsToDeltas( action, dx, dy );
02023 
02024     // in "continuous" mode, we accumulate the deltas each frame - thus
02025     // the deltas act more like speeds.
02026     if ( _continuous )
02027     {
02028         _continuous_dx += dx * 0.01;
02029         _continuous_dy += dy * 0.01;
02030     }
02031     else
02032     {
02033         handleMovementAction( action._type, dx, dy, view );
02034     }
02035 
02036     return true;
02037 }
02038 
02039 bool
02040 EarthManipulator::handleMouseClickAction( const Action& action )
02041 {
02042     //TODO.
02043     return false;
02044 }
02045 
02046 bool
02047 EarthManipulator::handleKeyboardAction( const Action& action, double duration )
02048 {
02049     double dx = 0, dy = 0;
02050 
02051     switch( action._dir )
02052     {
02053     case DIR_LEFT:  dx =  1; break;
02054     case DIR_RIGHT: dx = -1; break;
02055     case DIR_UP:    dy = -1; break;
02056     case DIR_DOWN:  dy =  1; break;
02057     }
02058 
02059     dx *= _settings->getKeyboardSensitivity();
02060     dy *= _settings->getKeyboardSensitivity();
02061 
02062     applyOptionsToDeltas( action, dx, dy );
02063 
02064     return handleAction( action, dx, dy, duration );
02065 }
02066 
02067 bool
02068 EarthManipulator::handleScrollAction( const Action& action, double duration )
02069 {
02070     const double scrollFactor = 1.5;
02071 
02072     double dx = 0, dy = 0;
02073 
02074     switch( action._dir )
02075     {
02076     case DIR_LEFT:  dx =  1; break;
02077     case DIR_RIGHT: dx = -1; break;
02078     case DIR_UP:    dy = -1; break;
02079     case DIR_DOWN:  dy =  1; break;
02080     }
02081 
02082     dx *= scrollFactor * _settings->getScrollSensitivity();
02083     dy *= scrollFactor * _settings->getScrollSensitivity();
02084 
02085     applyOptionsToDeltas( action, dx, dy );
02086 
02087     return handleAction( action, dx, dy, duration );
02088 }
02089 
02090 bool
02091 EarthManipulator::handleAction( const Action& action, double dx, double dy, double duration )
02092 {
02093     bool handled = true;
02094 
02095     //if ( osgEarth::getNotifyLevel() > osg::INFO )
02096     //    dumpActionInfo( action, osg::DEBUG_INFO );
02097 
02098     //OE_NOTICE << "action=" << action << ", dx=" << dx << ", dy=" << dy << std::endl;
02099 
02100     switch( action._type )
02101     {
02102     case ACTION_HOME:
02103         if ( _homeViewpoint.isSet() )
02104         {
02105             setViewpoint( _homeViewpoint.value(), _homeViewpointDuration );
02106         }
02107         //else
02108         //{
02109         //    if ( getAutoComputeHomePosition() )
02110         //        computeHomePosition();
02111         //    setByLookAt( _homeEye, _homeCenter, _homeUp );
02112         //}
02113         break;
02114 
02115 
02116     case ACTION_PAN:
02117     case ACTION_PAN_LEFT:
02118     case ACTION_PAN_RIGHT:
02119     case ACTION_PAN_UP:
02120     case ACTION_PAN_DOWN:
02121         _task->set( TASK_PAN, dx, dy, duration );
02122         break;
02123 
02124     case ACTION_ROTATE:
02125     case ACTION_ROTATE_LEFT:
02126     case ACTION_ROTATE_RIGHT:
02127     case ACTION_ROTATE_UP:
02128     case ACTION_ROTATE_DOWN:
02129         _task->set( TASK_ROTATE, dx, dy, duration );
02130         break;
02131 
02132     case ACTION_ZOOM:
02133     case ACTION_ZOOM_IN:
02134     case ACTION_ZOOM_OUT:
02135         _task->set( TASK_ZOOM, dx, dy, duration );
02136         break;
02137 
02138     default:
02139         handled = false;
02140     }
02141 
02142     return handled;
02143 }
02144 
02145 void
02146 EarthManipulator::recalculateRoll()
02147 {
02148     osg::Matrixd rotation_matrix;
02149     rotation_matrix.makeRotate(_centerRotation);
02150 
02151     osg::Vec3d lookVector = -getUpVector(rotation_matrix);
02152     osg::Vec3d upVector = getFrontVector(rotation_matrix);
02153 
02154     osg::CoordinateFrame coordinateFrame = getMyCoordinateFrame( _center ); //getCoordinateFrame(_center);
02155     osg::Vec3d localUp = getUpVector(coordinateFrame);
02156 
02157     osg::Vec3d sideVector = lookVector ^ localUp;
02158 
02159     if (sideVector.length()<0.1)
02160     {
02161         //OE_INFO<<"Side vector short "<<sideVector.length()<<std::endl;
02162 
02163         sideVector = upVector^localUp;
02164         sideVector.normalize();
02165 
02166     }
02167 
02168     osg::Vec3d newUpVector = sideVector^lookVector;
02169     newUpVector.normalize();
02170 
02171     osg::Quat rotate_roll;
02172     rotate_roll.makeRotate(upVector,newUpVector);
02173 
02174     if (!rotate_roll.zeroRotation())
02175     {
02176         _centerRotation = _centerRotation * rotate_roll;
02177     }
02178 }
02179 
02180 double
02181 EarthManipulator::getAzimuth() const
02182 {
02183         osg::Matrix m = getMatrix() * osg::Matrixd::inverse( getMyCoordinateFrame( _center ) ); //getCoordinateFrame( _center ) );
02184     osg::Vec3d look = -getUpVector( m ); // -m(2,0), -m(2,1), -m(2,2)
02185     osg::Vec3d up   =  getFrontVector( m );
02186     //osg::Vec3d look( -m(2,0), -m(2,1), -m(2,2) );
02187     
02188     look.normalize();
02189     up.normalize();
02190 
02191     double azim;    
02192     if ( look.z() < -0.9 )
02193         azim = atan2( up.x(), up.y() );
02194     else if ( look.z() > 0.9 )
02195         azim = atan2( -up.x(), -up.y() );
02196     else
02197         azim = atan2( look.x(), look.y() );
02198 
02199     return normalizeAzimRad( azim );
02200 }
02201 
02202 
02203 void
02204 EarthManipulator::recalculateLocalPitchAndAzimuth()
02205 {
02206         double r;
02207         s_getHPRFromQuat( _rotation, _local_azim, _local_pitch, r);
02208         _local_pitch -= osg::PI_2;
02209         //OE_NOTICE << "Azim=" << osg::RadiansToDegrees(_local_azim) << " Pitch=" << osg::RadiansToDegrees(_local_pitch) << std::endl;
02210 }
02211 
02212 void
02213 EarthManipulator::setHomeViewpoint( const Viewpoint& vp, double duration_s )
02214 {
02215     _homeViewpoint = vp;
02216     _homeViewpointDuration = duration_s;
02217 }
02218 
02219 // Find the point on a line, specified by p1 and v, closest to another
02220 // point.
02221 osg::Vec3d closestPtOnLine(const osg::Vec3d& p1, const osg::Vec3d& v,
02222                            const osg::Vec3d& p)
02223 {
02224     double u = (p - p1) * v / v.length2();
02225     return p1 + v * u;
02226 }
02227 
02228 // Intersection of line and plane
02229 bool findIntersectionWithPlane(const osg::Vec3d& normal, const osg::Vec3d& pt,
02230                                const osg::Vec3d& p1, const osg::Vec3d& v,
02231                                osg::Vec3d& result)
02232 {
02233     double denom = normal * v;
02234     if (osg::equivalent(0, denom))
02235         return false;
02236     double u = normal * (pt - p1) / denom;
02237     result = p1 + v * u;
02238     return true;
02239 }
02240 
02241 // Circle of intersection of two spheres. The circle is in the plane
02242 // normal to the line between the centers.
02243 bool sphereInterection(const osg::Vec3d& p0, double r0,
02244                        const osg::Vec3d& p1, double r1,
02245                        osg::Vec3d& resultCenter, double& r)
02246 {
02247     using namespace osg;
02248     Vec3d ptvec = (p1 - p0);
02249     double d = ptvec.normalize();
02250     if (d > r0 + r1)
02251         return false;               // spheres are too far apart
02252     else if (d < fabs(r0 - r1))
02253         return false;               // One sphere is contained in the other
02254     else if (equivalent(0, d) && equivalent(r0, r1))
02255     {
02256         resultCenter = p0;
02257         r = r0;
02258         return true;              // circles are coincident.
02259     }
02260     // distance from p0 to the line through the interection points
02261     double a = (r0 * r0 - r1 * r1 + d * d) / (2 * d);
02262     // distance from bisection of that line to the intersections
02263     resultCenter = p0 + ptvec * a;
02264     r = sqrt(r0 * r0 - a * a);
02265     return true;
02266 }
02267 
02268 // Find a point on the sphere (center, radius) through which the tangent
02269 // through pt passes. The point lies in the plane defined by
02270 //the line pt->center and ray.
02271 osg::Vec3d calcTangentPoint(const osg::Vec3d& pt, const osg::Vec3d& center,
02272                             double radius, const osg::Vec3d& ray)
02273 {
02274     using namespace osg;
02275     // new sphere with center at midpoint between pt and input sphere
02276     Vec3d center2 = (pt + center) / 2.0;
02277     double rad2 = (pt - center2).length();
02278     Vec3d resCtr;
02279     double resRad;
02280     // Use Thales' theorem, which states that a triangle inscribed in
02281     // a circle, with two points on a diameter of the circle and the
02282     // third on the circle, is a right triangle. Since one endpoint is
02283     // the center of the original sphere (the earth) and the other is
02284     // pt, we can get our tangent from that.
02285     bool valid = sphereInterection(center, radius, center2, rad2, resCtr,
02286                                    resRad);
02287     if (!valid)
02288         return Vec3d(0.0, 0.0, 0.0);
02289     // Get the tangent point that lies in the plane of the ray and the
02290     // center line. The sequence of cross products gives us the point
02291     // that is closest to the ray, rather than the one on the other
02292     // side of the sphere.
02293     Vec3d toCenter = center - pt;
02294     toCenter.normalize();
02295     Vec3d normal = ray ^ toCenter;
02296     normal.normalize();
02297     Vec3d radial = toCenter ^ normal;
02298     radial = radial * resRad;
02299     Vec3d result = resCtr + radial;
02300     return result;
02301     
02302 }
02303 // Calculate a pointer click in eye coordinates
02304 osg::Vec3d getWindowPoint(osgViewer::View* view, float x, float y)
02305 {
02306     float local_x, local_y;
02307     const osg::Camera* camera
02308         = view->getCameraContainingPosition(x, y, local_x, local_y);
02309     if (!camera)
02310         camera = view->getCamera();
02311     osg::Matrix winMat;
02312     if (camera->getViewport())
02313         winMat = camera->getViewport()->computeWindowMatrix();
02314     osg::Matrix projMat = camera->getProjectionMatrix();
02315     // ray from eye through pointer in camera coordinate system goes
02316     // from origin through transformed pointer coordinates
02317     osg::Matrix win2camera = projMat * winMat;
02318     win2camera.invert(win2camera);
02319     osg::Vec4d winpt4 = osg::Vec4d(x, y, 0.0, 1.0) * win2camera;
02320     winpt4 = winpt4 / winpt4.w();
02321     return osg::Vec3d(winpt4.x(), winpt4.y(), winpt4.z());
02322 }
02323 
02324 // Decompose  _center and _centerRotation into a longitude rotation
02325 // and a latitude rotation + translation in the longitudinal plane.
02326 
02327 void decomposeCenter(const osg::Vec3d& center, const osg::Quat& centerRotation,
02328                      osg::Matrix& Me, osg::Matrix& Mlon)
02329 {
02330     using namespace osg;
02331     Mlon.makeIdentity();
02332     Matrix Mtotal(centerRotation);
02333     Mtotal.setTrans(center);
02334     // Use the X axis to determine longitude rotation. Due to the
02335     // OpenGL camera rotation, this axis will be the Y axis of the
02336     // longitude matrix.
02337     Mlon(1, 0) = Mtotal(0, 0);  Mlon(1, 1) = Mtotal(0, 1);
02338     // X axis is rotated 90 degrees, obviously
02339     Mlon(0, 0) = Mlon(1, 1);  Mlon(0, 1) = -Mlon(1, 0);
02340     Matrix MlonInv = Matrixd::inverse(Mlon);
02341     Me = Mtotal * MlonInv;
02342 }
02343 
02344 osg::Matrixd rotateAroundPoint(const osg::Vec3d& pt, double theta,
02345                                const osg::Vec3d& axis)
02346 {
02347     return (osg::Matrixd::translate(pt)
02348             * osg::Matrixd::rotate(theta, axis)
02349             * osg::Matrixd::translate(pt * -1.0));
02350 }
02351 
02352 // Theory of operation for the manipulator drag motion
02353 //
02354 // The mouse drag is transformed to a vector on the surface of the
02355 // earth i.e., in the surface plane at the start of the drag. This is
02356 // treated as a displacement along the arc of a great circle. The
02357 // earth will be rotated by the equivalent rotation around the axis of
02358 // the circle. However, the manipulator controls the camera, not the
02359 // earth, so the camera's placement matrix (inverse view matrix)
02360 // should be rotated by the inverse of the calculated
02361 // rotation. EarthManipulator represents the placement matrix as the
02362 // concatenation of 4 transformations: distance from focal point,
02363 // local heading and pitch, rotation to frame of focal point, focal
02364 // point. To change the camera placement we rotate the frame rotation
02365 // (_centerRotation) and focal point (_center).
02366 //
02367 // When the start or end drag click is not on the earth, we choose the
02368 // nearest tangent point on the earth to the ray from the eye and
02369 // proceed.
02370 
02371 void
02372 EarthManipulator::drag(double dx, double dy, osg::View* theView)
02373 {
02374     using namespace osg;
02375     const osg::Vec3d zero(0.0, 0.0, 0.0);
02376     if (_last_action._type != ACTION_EARTH_DRAG)
02377         _lastPointOnEarth = zero;
02378 
02379     ref_ptr<osg::CoordinateSystemNode> csnSafe = _csn.get();
02380     double radiusEquator = csnSafe.valid() ? csnSafe->getEllipsoidModel()->getRadiusEquator() : 6378137.0;
02381 
02382     osgViewer::View* view = dynamic_cast<osgViewer::View*>(theView);
02383     float x = _ga_t0->getX(), y = _ga_t0->getY();
02384     float local_x, local_y;
02385     const osg::Camera* camera
02386         = view->getCameraContainingPosition(x, y, local_x, local_y);
02387     if (!camera)
02388         camera = view->getCamera();
02389     osg::Matrix viewMat = camera->getViewMatrix();
02390     osg::Matrix viewMatInv = camera->getInverseViewMatrix();
02391     if (!_ga_t1.valid())
02392         return;
02393     osg::Vec3d worldStartDrag;
02394     // drag start in camera coordinate system.
02395     bool onEarth;
02396     if ((onEarth = screenToWorld(_ga_t1->getX(), _ga_t1->getY(),
02397                                   view, worldStartDrag)))
02398     {
02399         if (_lastPointOnEarth == zero)
02400             _lastPointOnEarth = worldStartDrag;
02401         else
02402             worldStartDrag = _lastPointOnEarth;
02403     }
02404     else if (_is_geocentric)
02405     {
02406         if (_lastPointOnEarth != zero)
02407         {
02408             worldStartDrag =_lastPointOnEarth;
02409         }
02410         else if (csnSafe.valid())
02411         {
02412             const osg::Vec3d startWinPt = getWindowPoint(view, _ga_t1->getX(),
02413                                                          _ga_t1->getY());
02414             const osg::Vec3d startDrag = calcTangentPoint(
02415                 zero, zero * viewMat, radiusEquator,
02416                 startWinPt);
02417             worldStartDrag = startDrag * viewMatInv;
02418         }
02419     }
02420     else
02421         return;
02422     // ray from eye through pointer in camera coordinate system goes
02423     // from origin through transformed pointer coordinates
02424     const osg::Vec3d winpt = getWindowPoint(view, x, y);
02425     // Find new point to which startDrag has been moved
02426     osg::Vec3d worldEndDrag;
02427     osg::Quat worldRot;
02428     bool endOnEarth = screenToWorld(x, y, view, worldEndDrag);
02429     if (endOnEarth)
02430     {
02431         // OE_WARN << "end drag: " << worldEndDrag << "\n";
02432     }
02433     else
02434     {
02435         Vec3d earthOrigin = zero * viewMat;
02436         const osg::Vec3d endDrag = calcTangentPoint(
02437             zero, earthOrigin, radiusEquator, winpt);
02438         worldEndDrag = endDrag * viewMatInv;
02439         OE_INFO << "tangent: " << worldEndDrag << "\n";
02440     }
02441 
02442 #if 0
02443     if (onEarth != endOnEarth)
02444     {
02445         std::streamsize oldPrecision = osgEarth::notify(INFO).precision(10);
02446         OE_INFO << (onEarth ? "leaving earth\n" : "entering earth\n");
02447         OE_INFO << "start drag: " << worldStartDrag.x() << " "
02448                 << worldStartDrag.y() << " "
02449                 << worldStartDrag.z() << "\n";
02450         OE_INFO << "end drag: " << worldEndDrag.x() << " "
02451                 << worldEndDrag.y() << " "
02452                 << worldEndDrag.z() << "\n";
02453         osgEarth::notify(INFO).precision(oldPrecision);
02454     }
02455 #endif
02456 
02457     if (_is_geocentric)
02458     {
02459         worldRot.makeRotate(worldStartDrag, worldEndDrag);
02460         // Move the camera by the inverse rotation
02461         Quat cameraRot = worldRot.conj();
02462         // Derive manipulator parameters from the camera matrix. We
02463         // can't use _center, _centerRotation, and _rotation directly
02464         // from the manipulator because they may have been updated
02465         // already this frame while the camera view matrix,
02466         // used to do the terrain intersection, has not. This happens
02467         // when several mouse movement events arrive in a frame. there
02468         // will be bad stuttering artifacts if we use the updated
02469         // manipulator parameters.
02470         Matrixd Mmanip = Matrixd::translate(_offset_x, _offset_y, -_distance)
02471             * viewMatInv;
02472         Vec3d center = Mmanip.getTrans();
02473         Quat centerRotation = makeCenterRotation(center);
02474         Matrixd Mrotation = (Mmanip * Matrixd::translate(center * -1)
02475                              * Matrixd::rotate(centerRotation.inverse()));
02476         Matrixd Me = Matrixd::rotate(centerRotation)
02477             * Matrixd::translate(center) * Matrixd::rotate(cameraRot);
02478         // In order for the Viewpoint settings to make sense, the
02479         // inverse camera matrix must not have a roll component, which
02480         // implies that its x axis remains parallel to the
02481         // z = 0 plane. The strategy for doing that is different if
02482         // the azimuth is locked.
02483         // Additionally, the part of the camera rotation defined by
02484         // _centerRotation must be oriented with the local frame of
02485         // _center on the ellipsoid. For the purposes of the drag
02486         // motion this is nearly identical to the frame obtained by
02487         // the trackball motion, so we just fix it up at the end.
02488         if (_settings->getLockAzimuthWhilePanning())
02489         {
02490             // The camera needs to be rotated that _centerRotation
02491             // is a rotation only around the global Z axis and the
02492             // camera frame X axis. We don't change _rotation, so that
02493             // azimuth and pitch will stay constant, but the drag must
02494             // still be correct i.e.,  the point dragged must remain
02495             // under the cursor. Therefore the rotation must be around the
02496             // point that was dragged, worldEndDrag.
02497             //
02498             // Rotate Me so that its x axis is parallel to the z=0
02499             // plane. 
02500             // Find cone with worldEndDrag->center axis and x
02501             // axis of coordinate frame as generator of the conical
02502             // surface.
02503             Vec3d coneAxis = worldEndDrag * -1;
02504             coneAxis.normalize();
02505             Vec3d xAxis(Me(0, 0), Me(0, 1), Me(0, 2));
02506             // Center of disk: project xAxis onto coneAxis
02507             double diskDist = xAxis * coneAxis;
02508             Vec3d P1 = coneAxis * diskDist;
02509             // Basis of disk equation:
02510             // p = P1 + R * r * cos(theta) + S * r * sin(theta)
02511             Vec3d R = xAxis - P1;
02512             Vec3d S = R ^ coneAxis;
02513             double r = R.normalize();
02514             S.normalize();
02515             // Solve for angle that rotates xAxis into z = 0 plane.
02516             // soln to 0 = P1.z + r cos(theta) R.z + r sin(theta) S.z
02517             double temp1 = r * (square(S.z()) + square(R.z()));
02518             if (equivalent(temp1, 0.0))
02519                 return;
02520             double radical = r * temp1 - square(P1.z());
02521             if (radical < 0)
02522                 return;
02523             double temp2 = R.z() * sqrt(radical) / temp1;
02524             double temp3 = S.z() * P1.z() / temp1;
02525             double sin1 = temp2 + temp3;
02526             double sin2 = temp2 - temp3;
02527             double theta1 = DBL_MAX;
02528             double theta2 = DBL_MAX;
02529             Matrixd cm1, cm2;
02530             if (fabs(sin1) <= 1.0)
02531             {
02532                 theta1 = -asin(sin1);
02533                 Matrixd m = rotateAroundPoint(worldEndDrag, -theta1, coneAxis);
02534                 cm1 = Me * m;
02535             }
02536             if (fabs(sin2) <= 1.0)
02537             {
02538                 theta2 = asin(sin2);
02539                 Matrix m = rotateAroundPoint(worldEndDrag, -theta2, coneAxis);
02540                 cm2 = Me * m;
02541             }
02542             if (theta1 == DBL_MAX && theta2 == DBL_MAX)
02543                 return;
02544             Matrixd* CameraMat = 0;
02545             if (theta1 != DBL_MAX && cm1(1, 2) >= 0.0)
02546                 CameraMat = &cm1;
02547             else if (theta2 != DBL_MAX && cm2(1, 2) >= 0.0)
02548                 CameraMat = &cm2;
02549             else
02550                 return;
02551             _center = CameraMat->getTrans();
02552         }
02553         else
02554         {
02555             // The camera matrix must be rotated around the local Z axis so
02556             // that the X axis is parallel to the global z = 0
02557             // plane. Then, _rotation is rotated by the inverse
02558             // rotation to preserve the total transformation.
02559             double theta = atan2(-Me(0, 2), Me(1, 2));
02560             double s = sin(theta), c = cos(theta);
02561             if (c * Me(1, 2) - s * Me(0, 2) < 0.0)
02562             {
02563                 s = -s;
02564                 c = -c;
02565             }
02566             Matrixd m(c, s, 0, 0,
02567                       -s, c, 0, 0,
02568                       0, 0, 1, 0,
02569                       0, 0, 0, 1);
02570             Matrixd CameraMat = m * Me;
02571             _center = CameraMat.getTrans();
02572             // It's not necessary to include the translation
02573             // component, but it's useful for debugging.
02574             Matrixd headMat
02575                 = (Matrixd::translate(-_offset_x, -_offset_y, _distance)
02576                    * Mrotation);
02577             headMat = headMat * Matrixd::inverse(m);
02578             _rotation = headMat.getRotate();
02579             recalculateLocalPitchAndAzimuth();
02580         }
02581         _centerRotation = makeCenterRotation(_center);
02582         CoordinateFrame local_frame = getMyCoordinateFrame(_center);
02583         _previousUp = getUpVector(local_frame);
02584     }
02585     else
02586     {
02587         // This is obviously not correct.
02588         _center = _center + (worldStartDrag - worldEndDrag);
02589     }
02590 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines