diff --git a/include/osgGA/UFOManipulator b/include/osgGA/UFOManipulator new file mode 100644 index 000000000..61d5003ca --- /dev/null +++ b/include/osgGA/UFOManipulator @@ -0,0 +1,165 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGGA_UFO_MANIPULATOR_DEF +#define OSGGA_UFO_MANIPULATOR_DEF 1 + +#include + +#include + +#include +#include +#include + + /** + \class UFOManipulator + \brief A UFO manipulator driven with keybindings. + + The UFOManipulator is better suited for applications that employ + architectural walk-throughs, or situations where the eyepoint motion + model must move slowly, deliberately and well controlled. + + The UFO Manipulator allows the following movements with the listed + Key combinations: + \param UpArrow Acceleration forward. + \param DownArrow Acceleration backward (or deceleration forward). + \param LeftArrow Rotate view and direction of travel to the left. + \param RightArrow Rotate view and direction of travel to the right. + \param SpaceBar Brake. Gradually decelerates linear and rotational movement. + \param Shift/UpArrow Accelerate up. + \param Shift/DownArrow Accelerate down. + \param Shift/LeftArrow Accelerate (linearly) left. + \param Shift/RightArrow Accelerate (linearly) right. + \param Shift/SpaceBar Instant brake. Immediately stop all linear and rotational movement. + + When the Shift key is released, up, down, linear left and/or linear right movement is decelerated. + + \param Ctrl/UpArrow Rotate view (but not direction of travel) up. + \param Ctrl/DownArrow Rotate view (but not direction of travel) down. + \param Ctrl/LeftArrow Rotate view (but not direction of travel) left. + \param Ctrl/RightArrow Rotate view (but not direction of travel) right. + \param Ctrl/Return Straightens out the view offset. + + */ + +namespace osgGA { + +class OSGGA_EXPORT UFOManipulator : public osgGA::MatrixManipulator +{ + + public: + /** Default constructor */ + UFOManipulator(); + + /** return className + \return returns constant "UFOManipulator" + */ + virtual const char* className() const; + + /** Set the current position with a matrix + \param matrix A viewpoint matrix. + */ + virtual void setByMatrix( const osg::Matrix &matrix ) ; + + /** Set the current position with the invers matrix + \param invmatrix The inverse of a viewpoint matrix + */ + virtual void setByInverseMatrix( const osg::Matrix &invmat); + + /** Get the current viewmatrix */ + virtual osg::Matrix getMatrix() const; + + /** Get the current inverse view matrix */ + virtual osg::Matrix getInverseMatrix() const ; + + /** Set the subgraph this manipulator is driving the eye through. + \param node root of subgraph + */ + virtual void setNode(osg::Node*); + + /** Get the root node of the subgraph this manipulator is driving the eye through (const)*/ + virtual const osg::Node* getNode() const; + + /** Get the root node of the subgraph this manipulator is driving the eye through */ + virtual osg::Node* getNode(); + + /** Computes the home position based on the extents and scale of the + scene graph rooted at node */ + virtual void computeHomePosition(); + + /** Sets the viewpoint matrix to the home position */ + virtual void home(const osgGA::GUIEventAdapter&, osgGA::GUIActionAdapter&) ; + + /** Handles incoming osgGA events */ + bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter &aa); + + /** Reports Usage parameters to the application */ + void getUsage(osg::ApplicationUsage& usage) const; + + protected: + + private: + osg::ref_ptr _node; + float _viewAngle; + osg::Matrix _matrix; + osg::Matrix _inverseMatrix; + osg::Matrix _offset; + + double _minHeightAboveGround; + double _minDistanceInFront; + + double _speedEpsilon; + double _forwardSpeed; + double _sideSpeed; + double _upSpeed; + double _speedAccelerationFactor; + double _speedDecelerationFactor; + + bool _decelerateUpSideRate; + + double _directionRotationEpsilon; + double _directionRotationRate; + double _directionRotationAcceleration; + double _directionRotationDeceleration; + + double _viewOffsetDelta; + double _pitchOffsetRate; + double _pitchOffset; + double _yawOffsetRate; + double _yawOffset; + + double _t0; + double _dt; + osg::Vec3 _direction; + osg::Vec3 _position; + + + bool _shift; + bool _ctrl; + bool _decelerateOffsetRate; + + bool _straightenOffset; + + void _home(); + void _stop(); + void _keyDown( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &); + void _keyUp( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter &); + void _frame(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter &); + + void _adjustPosition(); +}; + +} + +#endif diff --git a/src/osgGA/GNUmakefile b/src/osgGA/GNUmakefile index beafb537f..3ce88368e 100644 --- a/src/osgGA/GNUmakefile +++ b/src/osgGA/GNUmakefile @@ -8,6 +8,7 @@ CXXFILES = \ DriveManipulator.cpp\ EventVisitor.cpp\ FlightManipulator.cpp\ + UFOManipulator.cpp\ GUIEventAdapter.cpp\ GUIEventHandler.cpp\ GUIEventHandlerVisitor.cpp\ diff --git a/src/osgGA/UFOManipulator.cpp b/src/osgGA/UFOManipulator.cpp new file mode 100644 index 000000000..091bccd7f --- /dev/null +++ b/src/osgGA/UFOManipulator.cpp @@ -0,0 +1,511 @@ +#include +#include + +using namespace osgGA; + +UFOManipulator::UFOManipulator(): + _t0(0.0), + _shift(false), + _ctrl(false) +{ + _minHeightAboveGround = 2.0; + _minDistanceInFront = 5.0; + + _speedAccelerationFactor = 0.4; + _speedDecelerationFactor = 0.90; + + _directionRotationRate = 0.0; + _directionRotationAcceleration = M_PI*0.00005; + _directionRotationDeceleration = 0.90; + + _speedEpsilon = 0.02; + _directionRotationEpsilon = 0.0001; + + _viewOffsetDelta = M_PI * 0.0025; + _pitchOffsetRate = 0.0; + _pitchOffset = 0.0; + + _yawOffsetRate = 0.0; + _yawOffset = 0.0; + _offset.makeIdentity(); + + _decelerateOffsetRate = true; + _straightenOffset = false; + + _direction.set( 0,1,0); + _stop(); +} + +void UFOManipulator::setNode( osg::Node *node ) +{ + _node = node; + + if (getAutoComputeHomePosition()) + computeHomePosition(); + + _home(); +} + +const osg::Node* UFOManipulator::getNode() const +{ + return _node.get(); +} + +osg::Node* UFOManipulator::getNode() +{ + return _node.get(); +} + + +const char* UFOManipulator::className() const +{ + return "UFOManipulator"; +} + +void UFOManipulator::setByMatrix( const osg::Matrix &mat ) +{ + _inverseMatrix = mat; + _matrix.invert( _inverseMatrix ); +} + +void UFOManipulator::setByInverseMatrix( const osg::Matrix &invmat) +{ + _matrix = invmat; + _inverseMatrix.invert( _matrix ); +} + +osg::Matrix UFOManipulator::getMatrix() const +{ + return (_offset * _matrix); +} + +osg::Matrix UFOManipulator::getInverseMatrix() const +{ + return (_inverseMatrix * _offset); +} + +void UFOManipulator::computeHomePosition() +{ + if( !_node.valid() ) + return; + + osg::BoundingSphere bs = _node->getBound(); + + /* + * Find the ground - Assumption: The ground is the hit of an intersection + * from a line segment extending from above to below the database at its + * horizontal center, that intersects the database closest to zero. */ + osgUtil::IntersectVisitor iv; + osg::ref_ptr seg = new osg::LineSegment; + osg::Vec3 A = bs.center() + (osg::Vec3(0,0,1)*(bs.radius()*2)); + osg::Vec3 B = bs.center() + (osg::Vec3(0,0,-1)*(bs.radius()*2)); + + if( (B-A).length() == 0.0) + { + puts( "DOH" ); fflush(stdout); + return; + } + + /* + seg->set( bs.center() + (osg::Vec3(0,0,1)*(bs.radius()*2)), + bs.center() + (osg::Vec3(0,0,-1)*(bs.radius()*2)) ); + */ + seg->set( A, B ); + + iv.addLineSegment( seg.get() ); + _node->accept(iv); + + // start with it high + double ground = bs.radius() * 3; + + if (iv.hits()) + { + osgUtil::IntersectVisitor::HitList& hitList = iv.getHitList(seg.get()); + for(osgUtil::IntersectVisitor::HitList::iterator hitr=hitList.begin(); + hitr!=hitList.end(); ++hitr) + { + osg::Vec3d ip = hitList.front().getWorldIntersectPoint(); + if( fabs(ip[3]) < ground ) + ground = ip[3]; + } + } + else + { + osg::notify(osg::WARN)<<"UFOManipulator : I can't find the ground!"<", "Reset the viewing angle to 0.0"); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Acceleration forward."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Acceleration backward (or deceleration forward"); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Rotate view and direction of travel to the left."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Rotate view and direction of travel to the right."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Brake. Gradually decelerates linear and rotational movement."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Accelerate up."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Accelerate down."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Accelerate (linearly) left."); + usage.addKeyboardMouseBinding("UFO Manipulator: ","Accelerate (linearly) right."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Instant brake. Immediately stop all linear and rotational movement."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Rotate view (but not direction of travel) up."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Rotate view (but not direction of travel) down."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Rotate view (but not direction of travel) left."); + usage.addKeyboardMouseBinding("UFO Manipulator: ", "Rotate view (but not direction of travel) right."); +} + + + +void UFOManipulator::_keyUp( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter & ) +{ + switch( ea.getKey() ) + { + case Producer::KeyChar_Control_L: + case Producer::KeyChar_Control_R: + _ctrl = false; + _decelerateOffsetRate = true; + _straightenOffset = false; + break; + + case Producer::KeyChar_Shift_L: + case Producer::KeyChar_Shift_R: + _shift = false; + _decelerateUpSideRate = true; + break; + } +} + +void UFOManipulator::_keyDown( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter & ) +{ + switch( ea.getKey() ) + { + case Producer::KeyChar_Control_L: + case Producer::KeyChar_Control_R: + _ctrl = true; + break; + + case Producer::KeyChar_Shift_L : + case Producer::KeyChar_Shift_R : + _shift = true; + break; + + case Producer::KeyChar_Up: + if( _ctrl ) + { + _pitchOffsetRate -= _viewOffsetDelta; + _decelerateOffsetRate = false; + } + else + { + if( _shift ) + { + _upSpeed += _speedAccelerationFactor; + _decelerateUpSideRate = false; + } + else + _forwardSpeed += _speedAccelerationFactor; + } + break; + + case Producer::KeyChar_Down: + if( _ctrl ) + { + _pitchOffsetRate += _viewOffsetDelta; + _decelerateOffsetRate = false; + } + else + { + if( _shift ) + { + _upSpeed -= _speedAccelerationFactor; + _decelerateUpSideRate = false; + } + else + _forwardSpeed -= _speedAccelerationFactor; + } + break; + + case Producer::KeyChar_Right: + if( _ctrl ) + { + _yawOffsetRate += _viewOffsetDelta; + _decelerateOffsetRate = false; + } + else + { + if(_shift) + { + _sideSpeed += _speedAccelerationFactor; + _decelerateUpSideRate = false; + } + else + _directionRotationRate -= _directionRotationAcceleration; + } + break; + + case Producer::KeyChar_Left: + if( _ctrl ) + { + _yawOffsetRate -= _viewOffsetDelta; + _decelerateOffsetRate = false; + } + else + { + if(_shift) + { + _sideSpeed -= _speedAccelerationFactor; + _decelerateUpSideRate = false; + } + else + _directionRotationRate += _directionRotationAcceleration; + } + break; + + case Producer::KeyChar_Return: + if( _ctrl ) + { + _straightenOffset = true; + } + break; + + case ' ': + if( _shift ) + { + _stop(); + } + else + { + if( fabs(_forwardSpeed) > 0.0 ) + { + _forwardSpeed *= _speedDecelerationFactor; + + if( fabs(_forwardSpeed ) < _speedEpsilon ) + _forwardSpeed = 0.0; + } + if( fabs(_sideSpeed) > 0.0 ) + { + _sideSpeed *= _speedDecelerationFactor; + + if( fabs( _sideSpeed ) < _speedEpsilon ) + _sideSpeed = 0.0; + } + + if( fabs(_upSpeed) > 0.0 ) + { + _upSpeed *= _speedDecelerationFactor; + + if( fabs( _upSpeed ) < _speedEpsilon ) + _sideSpeed = 0.0; + } + + + if( fabs(_directionRotationRate ) > 0.0 ) + { + _directionRotationRate *= _directionRotationDeceleration; + if( fabs( _directionRotationRate ) < _directionRotationEpsilon ) + _directionRotationRate = 0.0; + } + + } + break; + + case 'H': + _home(); + break; + } +} + +void UFOManipulator::_frame( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter & ) +{ + double t1 = ea.time(); + if( _t0 == 0.0 ) + { + _t0 = ea.time(); + _dt = 0.0; + } + else + { + _dt = t1 - _t0; + _t0 = t1; + } + + if( fabs( _directionRotationRate ) > _directionRotationEpsilon ) + { + _direction = _direction * osg::Matrix::rotate( _directionRotationRate, osg::Vec3(0,0,1)); + } + + { + osg::Vec3 _sideVec = _direction * osg::Matrix::rotate( -M_PI*0.5, osg::Vec3(0,0,1)); + + _position += ((_direction * _forwardSpeed) + + (_sideVec * _sideSpeed) + + (osg::Vec3(0,0,1) * _upSpeed)) + * _dt; + } + + _pitchOffset += _pitchOffsetRate * _dt; + if( _pitchOffset >= M_PI || _pitchOffset < -M_PI ) + _pitchOffset *= -1; + + _yawOffset += _yawOffsetRate * _dt; + if( _yawOffset >= M_PI || _yawOffset < -M_PI ) + _yawOffset *= -1; + + _offset = osg::Matrix::rotate( _yawOffset, osg::Vec3(0,1,0), + _pitchOffset, osg::Vec3(1,0,0), + 0.0, osg::Vec3(0,0,1)); + + _adjustPosition(); + + _inverseMatrix.makeLookAt( _position, _position + _direction, osg::Vec3(0,0,1)); + _matrix.invert(_inverseMatrix); + + if( _decelerateUpSideRate ) + { + _upSpeed *= 0.98; + _sideSpeed *= 0.98; + } + + if( _decelerateOffsetRate ) + { + _yawOffsetRate *= 0.98; + _pitchOffsetRate *= 0.98; + } + + if( _straightenOffset ) + { + _pitchOffsetRate = 0.0; + _yawOffsetRate = 0.0; + _pitchOffset *= 0.99; + _yawOffset *= 0.99; + + if( fabs(_pitchOffset ) < 0.01 ) + _pitchOffset = 0.0; + if( fabs(_yawOffset ) < 0.01 ) + _pitchOffset = 0.0; + + if( _pitchOffset == 0.0 && _yawOffset == 0.0 ) + _straightenOffset = false; + } +} + +void UFOManipulator::_adjustPosition() +{ + if( !_node.valid() ) + return; + + osgUtil::IntersectVisitor iv; + + // Forward line segment at 3 times our intersect distance + osg::ref_ptr segForward = new osg::LineSegment; + segForward->set(_position, _position + (_direction * (_minDistanceInFront * 3.0)) ); + iv.addLineSegment( segForward.get() ); + + + // Down line segment at 3 times our intersect distance + osg::ref_ptr segDown = new osg::LineSegment; + segDown->set( _position, + _position - (osg::Vec3(0,0, _minHeightAboveGround*3))); + iv.addLineSegment( segDown.get() ); + + _node->accept(iv); + + if (iv.hits()) + { + // Check intersects infront. + { + osgUtil::IntersectVisitor::HitList& hitList = iv.getHitList(segForward.get()); + if (!hitList.empty()) + { + osg::Vec3d ip = hitList.front().getWorldIntersectPoint(); + + double d = (ip - _position).length(); + + if( d < _minDistanceInFront ) + { + osg::Vec3 op = _position; + _position = ip + (_direction * -_minDistanceInFront); + _stop(); + } + } + } + + // Check intersects below. + { + osgUtil::IntersectVisitor::HitList& hitList = iv.getHitList(segDown.get()); + if (!hitList.empty()) + { + osg::Vec3d ip = hitList.front().getWorldIntersectPoint(); + if( _position[2] - ip[2] < _minHeightAboveGround ) + _position[2] = ip[2] + _minHeightAboveGround; + } + } + + } +} + + +void UFOManipulator::_stop() +{ + _forwardSpeed = 0.0; + _sideSpeed = 0.0; + _upSpeed = 0.0; + _directionRotationRate = 0.0; +}