From 22fa18d585ad2c503ecf7b40c64eef773e94f004 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Tue, 19 May 2009 11:18:03 +0000 Subject: [PATCH] From Mojtaba Fathi, "I've developed a new manipulator which uses azimuth and zenith angles to rotate scene and so avoids Roll angle rotation, such that scene is always seen as Z upward." --- include/osgGA/SphericalManipulator | 144 ++++++++++ src/osgGA/CMakeLists.txt | 2 + src/osgGA/SphericalManipulator.cpp | 432 +++++++++++++++++++++++++++++ 3 files changed, 578 insertions(+) create mode 100644 include/osgGA/SphericalManipulator create mode 100644 src/osgGA/SphericalManipulator.cpp diff --git a/include/osgGA/SphericalManipulator b/include/osgGA/SphericalManipulator new file mode 100644 index 000000000..8790cd939 --- /dev/null +++ b/include/osgGA/SphericalManipulator @@ -0,0 +1,144 @@ + +#ifndef __SphericalManipulator_h__ +#define __SphericalManipulator_h__ + +#include +#include +#include + +namespace osgGA +{ + +class OSGGA_EXPORT SphericalManipulator : public MatrixManipulator +{ + public: + SphericalManipulator(); + + virtual const char* className() const { return "Spherical Manipulator"; } + + /** set the position of the matrix manipulator using a 4x4 Matrix.*/ + virtual void setByMatrix(const osg::Matrixd& matrix); + + /** set the position of the matrix manipulator using a 4x4 Matrix.*/ + virtual void setByInverseMatrix(const osg::Matrixd& matrix) { setByMatrix(osg::Matrixd::inverse(matrix)); } + + /** get the position of the manipulator as 4x4 Matrix.*/ + virtual osg::Matrixd getMatrix() const; + + /** get the position of the manipulator as a inverse matrix of the manipulator, typically used as a model view matrix.*/ + virtual osg::Matrixd getInverseMatrix() const; + + /** Get the FusionDistanceMode. Used by SceneView for setting up stereo convergence.*/ + virtual osgUtil::SceneView::FusionDistanceMode getFusionDistanceMode() const { return osgUtil::SceneView::USE_FUSION_DISTANCE_VALUE; } + + /** Get the FusionDistanceValue. Used by SceneView for setting up stereo convergence.*/ + virtual float getFusionDistanceValue() const { return _distance; } + + /** Attach a node to the manipulator. + Automatically detaches previously attached node. + setNode(NULL) detaches previously nodes. + Is ignored by manipulators which do not require a reference model.*/ + virtual void setNode(osg::Node*); + + /** Return node if attached.*/ + virtual const osg::Node* getNode() const; + + /** Return node if attached.*/ + virtual osg::Node* getNode(); + + /** Move the camera to the default position. + May be ignored by manipulators if home functionality is not appropriate.*/ + virtual void home(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us); + virtual void home(double); + + /** Start/restart the manipulator.*/ + virtual void init(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us); + + void zoomOn(const osg::BoundingSphere& bound); + + /** handle events, return true if handled, false otherwise.*/ + virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& us); + + /** Compute the home position.*/ + virtual void computeHomePosition(); + + void computeViewPosition(const osg::BoundingSphere& bound,double& scale,double& distance,osg::Vec3d& center); + + const osg::Vec3d& getCenter() const {return _center;} + double getDistance() const { return _distance; } + double getHomeDistance() const { return _homeDistance; } + float getAzimuth() const {return _azimuth;} + float getZenith() const {return _zenith;} + + /** get the minimum distance (as ratio) the eye point can be zoomed in */ + double getMinimumZoomScale() const { return _minimumZoomScale; } + + /** set the minimum distance (as ratio) the eye point can be zoomed in towards the + center before the center is pushed forward.*/ + void setMinimumZoomScale(double minimumZoomScale) {_minimumZoomScale=minimumZoomScale;} + + /** set the mouse scroll wheel zoom delta. + * Range -1.0 to +1.0, -ve value inverts wheel direction and zero switches off scroll wheel. */ + void setScroolWheelZoomDelta(double zoomDelta) { _zoomDelta = zoomDelta; } + + /** get the mouse scroll wheel zoom delta. */ + double getScroolWheelZoomDelta() const { return _zoomDelta; } + + /** Get the keyboard and mouse usage of this manipulator.*/ + virtual void getUsage(osg::ApplicationUsage& usage) const; + + void setCenter(const osg::Vec3d ¢er) {_center=center;} + bool setDistance(double distance); + void setAngles(float azimuth,float zenith); + + enum RotationMode + { + MODE_3D=0, + MODE_3D_HORIZONTAL, + MODE_3D_VERTICAL, + MODE_2D + }; + + RotationMode getRotationMode() const {return _rotationMode;} + void setRotationMode(RotationMode mode); + + static double computeAngles(const osg::Vec3d &vec,float& azimuth,float& zenith); + + protected: + + virtual ~SphericalManipulator(); + + /** Reset the internal GUIEvent stack.*/ + void flushMouseEventStack(); + /** Add the current mouse GUIEvent to internal stack.*/ + void addMouseEvent(const osgGA::GUIEventAdapter& ea); + + /** For the give mouse movement calculate the movement of the camera. + Return true is camera has moved and a redraw is required.*/ + bool calcMovement(); + + /** Check the speed at which the mouse is moving. + If speed is below a threshold then return false, otherwise return true.*/ + bool isMouseMoving(); + + // Internal event stack comprising last two mouse events. + osg::ref_ptr _ga_t1; + osg::ref_ptr _ga_t0; + + osg::ref_ptr _node; + + double _modelScale; + double _minimumZoomScale; + + bool _thrown; + + RotationMode _rotationMode; + osg::Vec3d _center; + double _distance; + float _azimuth; // angle from x axis in xy plane + float _zenith; // angle from z axis + double _homeDistance; + float _zoomDelta; +}; +} +#endif diff --git a/src/osgGA/CMakeLists.txt b/src/osgGA/CMakeLists.txt index b5d35686a..c69dc895d 100644 --- a/src/osgGA/CMakeLists.txt +++ b/src/osgGA/CMakeLists.txt @@ -23,6 +23,7 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/StateSetManipulator ${HEADER_PATH}/TerrainManipulator ${HEADER_PATH}/TrackballManipulator + ${HEADER_PATH}/SphericalManipulator ${HEADER_PATH}/UFOManipulator ${HEADER_PATH}/Version ${HEADER_PATH}/CameraViewSwitchManipulator @@ -45,6 +46,7 @@ ADD_LIBRARY(${LIB_NAME} StateSetManipulator.cpp TerrainManipulator.cpp TrackballManipulator.cpp + SphericalManipulator.cpp UFOManipulator.cpp Version.cpp CameraViewSwitchManipulator.cpp diff --git a/src/osgGA/SphericalManipulator.cpp b/src/osgGA/SphericalManipulator.cpp new file mode 100644 index 000000000..38c04faac --- /dev/null +++ b/src/osgGA/SphericalManipulator.cpp @@ -0,0 +1,432 @@ + +#include +#include +#include +#include + +using namespace osg; +using namespace osgGA; + +//-------------------------------------------------------------------------------------------------- +SphericalManipulator::SphericalManipulator() +{ + _modelScale = 0.01f; + _minimumZoomScale = 0.1f; + _thrown = false; + + _distance=1.0f; + _homeDistance=1.0f; + + _zoomDelta = 0.1f; + _azimuth=0; + _zenith=0; + + setRotationMode(MODE_3D); +} +//-------------------------------------------------------------------------------------------------- +SphericalManipulator::~SphericalManipulator() +{ +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::setNode(osg::Node* node) +{ + _node = node; + if (_node.get()) + { + const osg::BoundingSphere& boundingSphere=_node->getBound(); + _modelScale = boundingSphere._radius; + } + if (getAutoComputeHomePosition()) computeHomePosition(); +} +//-------------------------------------------------------------------------------------------------- +const osg::Node* SphericalManipulator::getNode() const +{ + return _node.get(); +} +//-------------------------------------------------------------------------------------------------- +osg::Node* SphericalManipulator::getNode() +{ + return _node.get(); +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::setRotationMode(RotationMode mode) +{ + if(_rotationMode == mode) + return; + + _rotationMode=mode; + + if(_rotationMode == MODE_2D) + _zenith=0; +} +//-------------------------------------------------------------------------------------------------- +bool SphericalManipulator::setDistance(double distance) +{ + if(distance <= 0) + return false; + + _distance=distance; + + return true; +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::setAngles(float azimuth,float zenith) +{ + _azimuth=azimuth; + if(_rotationMode != MODE_2D) + _zenith=zenith; +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::home(double /*currentTime*/) +{ + if(getAutoComputeHomePosition()) + computeHomePosition(); + + _azimuth=3*PI_2; + _zenith=0; + _center=_homeCenter; + _distance=_homeDistance; + + _thrown = false; +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::home(const GUIEventAdapter& ea ,GUIActionAdapter& us) +{ + home(ea.getTime()); + us.requestRedraw(); + us.requestContinuousUpdate(false); +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::init(const GUIEventAdapter& ,GUIActionAdapter& ) +{ + flushMouseEventStack(); +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::getUsage(osg::ApplicationUsage& usage) const +{ + usage.addKeyboardMouseBinding("Spherical: Space","Reset the viewing position to home"); + usage.addKeyboardMouseBinding("Spherical: SHIFT","Rotates vertically only"); + usage.addKeyboardMouseBinding("Spherical: ALT","Rotates horizontally only"); +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::zoomOn(const osg::BoundingSphere& bound) +{ + computeViewPosition(bound,_modelScale,_distance,_center); + _thrown = false; +} +//-------------------------------------------------------------------------------------------------- +bool SphericalManipulator::handle(const GUIEventAdapter& ea,GUIActionAdapter& us) +{ + switch(ea.getEventType()) + { + case(GUIEventAdapter::FRAME): + if (_thrown) + { + if (calcMovement()) us.requestRedraw(); + } + return false; + default: + break; + } + + if (ea.getHandled()) return false; + + switch(ea.getEventType()) + { + case(GUIEventAdapter::PUSH): + { + flushMouseEventStack(); + addMouseEvent(ea); + us.requestContinuousUpdate(false); + _thrown = false; + return true; + } + + case(GUIEventAdapter::RELEASE): + { + if (ea.getButtonMask()==0) + { + double timeSinceLastRecordEvent = _ga_t0.valid() ? (ea.getTime() - _ga_t0->getTime()) : DBL_MAX; + if (timeSinceLastRecordEvent>0.02) flushMouseEventStack(); + + if (isMouseMoving()) + { + if (calcMovement()) + { + us.requestRedraw(); + us.requestContinuousUpdate(true); + _thrown = true; + } + } + else + { + flushMouseEventStack(); + addMouseEvent(ea); + if (calcMovement()) us.requestRedraw(); + us.requestContinuousUpdate(false); + _thrown = false; + } + + } + else + { + flushMouseEventStack(); + addMouseEvent(ea); + if (calcMovement()) us.requestRedraw(); + us.requestContinuousUpdate(false); + _thrown = false; + } + return true; + } + + case(GUIEventAdapter::DRAG): + case(GUIEventAdapter::SCROLL): + { + addMouseEvent(ea); + if (calcMovement()) us.requestRedraw(); + us.requestContinuousUpdate(false); + _thrown = false; + return true; + } + + case(GUIEventAdapter::MOVE): + { + return false; + } + + case(GUIEventAdapter::KEYDOWN): + if (ea.getKey()== GUIEventAdapter::KEY_Space) + { + flushMouseEventStack(); + _thrown = false; + home(ea,us); + return true; + } + return false; + + case(GUIEventAdapter::FRAME): + if (_thrown) + { + if (calcMovement()) us.requestRedraw(); + } + return false; + + default: + return false; + } + return false; +} +//-------------------------------------------------------------------------------------------------- +bool SphericalManipulator::isMouseMoving() +{ + if (_ga_t0.get()==NULL || _ga_t1.get()==NULL) return false; + + static const float velocity = 0.1f; + + float dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized(); + float dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized(); + float len = sqrtf(dx*dx+dy*dy); + float dt = _ga_t0->getTime()-_ga_t1->getTime(); + + return (len>dt*velocity); +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::flushMouseEventStack() +{ + _ga_t1 = NULL; + _ga_t0 = NULL; +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::addMouseEvent(const GUIEventAdapter& ea) +{ + _ga_t1 = _ga_t0; + _ga_t0 = &ea; +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::setByMatrix(const osg::Matrixd& matrix) +{ + _center=osg::Vec3(0,0,-_distance)*matrix; + _azimuth=atan2(-matrix(0,0),matrix(0,1)); + if(_rotationMode != MODE_2D) + _zenith=acos(matrix(2,2)); +} +//-------------------------------------------------------------------------------------------------- +osg::Matrixd SphericalManipulator::getMatrix() const +{ + return + osg::Matrixd::translate(osg::Vec3d(0,0,_distance))* + osg::Matrixd::rotate(_zenith,1,0,0)* + osg::Matrixd::rotate(PI_2+_azimuth,0,0,1)* + osg::Matrixd::translate(_center); +} +//-------------------------------------------------------------------------------------------------- +osg::Matrixd SphericalManipulator::getInverseMatrix() const +{ + return + osg::Matrixd::translate(-_center)* + osg::Matrixd::rotate(PI_2+_azimuth,0,0,-1)* + osg::Matrixd::rotate(_zenith,-1,0,0)* + osg::Matrixd::translate(osg::Vec3d(0,0,-_distance)); +} +//-------------------------------------------------------------------------------------------------- +double SphericalManipulator::computeAngles(const osg::Vec3d &vec,float& azimuth,float& zenith) +{ + osg::Vec3d lv(vec); + double distance=lv.length(); + if(distance > 0) + lv/=distance; + + azimuth=atan2(lv.y(),lv.x()); + zenith=acos(lv.z()); + + return distance; +} +//-------------------------------------------------------------------------------------------------- +bool SphericalManipulator::calcMovement() +{ + // mouse scroll is only a single event + if (_ga_t0.get()==NULL) return false; + + float dx=0.0f; + float dy=0.0f; + unsigned int buttonMask=osgGA::GUIEventAdapter::NONE; + + if (_ga_t0->getEventType()==GUIEventAdapter::SCROLL) + { + dy = _ga_t0->getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_UP ? _zoomDelta : -_zoomDelta; + buttonMask=GUIEventAdapter::SCROLL; + } + else + { + + if (_ga_t1.get()==NULL) return false; + dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized(); + dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized(); + float distance = sqrtf(dx*dx + dy*dy); + + // return if movement is too fast, indicating an error in event values or change in screen. + if (distance>0.5) + { + return false; + } + + // return if there is no movement. + if (distance==0.0f) + { + return false; + } + + buttonMask = _ga_t1->getButtonMask(); + } + + if (buttonMask==GUIEventAdapter::LEFT_MOUSE_BUTTON) + { + // rotate camera. + + if(_rotationMode == MODE_2D) + { + float pxc = (_ga_t0->getXmax()+_ga_t0->getXmin())/2; + float pyc = (_ga_t0->getYmax()+_ga_t0->getYmin())/2; + + float px0 = _ga_t0->getX(); + float py0 = _ga_t0->getY(); + + float px1 = _ga_t1->getX(); + float py1 = _ga_t1->getY(); + + float angle=atan2(py1-pyc,px1-pxc)-atan2(py0-pyc,px0-pxc); + + _azimuth+=angle; + if(_azimuth < -PI) + _azimuth+=2*PI; + else if(_azimuth > PI) + _azimuth-=2*PI; + } + else + { + if((_rotationMode != MODE_3D_VERTICAL) && ((_ga_t1->getModKeyMask() & GUIEventAdapter::MODKEY_SHIFT) == 0)) + { + _azimuth-=dx*PI_2; + + if(_azimuth < 0) + _azimuth+=2*PI; + else if(_azimuth > 2*PI) + _azimuth-=2*PI; + } + + if((_rotationMode != MODE_3D_HORIZONTAL) && ((_ga_t1->getModKeyMask() & GUIEventAdapter::MODKEY_ALT) == 0)) + { + _zenith+=dy*PI_4; + + // Only allows vertical rotation of 180deg + if(_zenith < 0) + _zenith=0; + else if(_zenith > PI) + _zenith=PI; + } + } + + return true; + } + else if (buttonMask==GUIEventAdapter::MIDDLE_MOUSE_BUTTON || + buttonMask==(GUIEventAdapter::LEFT_MOUSE_BUTTON|GUIEventAdapter::RIGHT_MOUSE_BUTTON)) + { + // pan model. + + float scale = -0.3f*_distance; + + osg::Matrix rotation_matrix; + rotation_matrix=osg::Matrixd::rotate(_zenith,1,0,0)*osg::Matrixd::rotate(PI_2+_azimuth,0,0,1); + + osg::Vec3 dv(dx*scale,dy*scale,0); + _center += dv*rotation_matrix; + + return true; + + } + else if (buttonMask==GUIEventAdapter::RIGHT_MOUSE_BUTTON || _ga_t0->getEventType()==GUIEventAdapter::SCROLL) + { + + // zoom model. + + float fd = _distance; + float scale = 1.0f+dy; + if(fd*scale > _modelScale*_minimumZoomScale) + { + _distance *= scale; + } + else + { + notify(osg::DEBUG_INFO) << "Pushing forward"<getBound(),_modelScale,_homeDistance,_homeCenter); +} +//-------------------------------------------------------------------------------------------------- +void SphericalManipulator::computeViewPosition(const osg::BoundingSphere& bound, + double& scale,double& distance,osg::Vec3d& center) +{ + scale=bound._radius; + distance=3.5*bound._radius; + if(distance <= 0) + distance=1; + center=bound._center; +}