osgEarth 2.1.1
|
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 }