Hi guys,
I finally upload the FirstPersonManipulator.
This the renamed and modified version of previously called DoomLike
Manipulator.
Hope kind users would not mind test it and make remarks or even better
recode the crappy parts of the current proposal :-).
Hope it will be of soe interest to someone.
Thanks a lot.
--
Loïc Simon
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 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_DOOM_LIKE_MANIPULATOR_DEF
#define OSGGA_DOOM_LIKE_MANIPULATOR_DEF 1
#include <iostream>
#include <osgGA/MatrixManipulator>
#include <osg/Node>
#include <osg/Matrix>
/**
\class osgGA::FirstPersonManipulator
\brief A FirstPerson manipulator driven with keybindings.
The FirstPersonManipulator is better suited for applications that employ architectural walk-throughs.
The camera control is fone via keyboard arrows concerning the position and via mouse draging concerning the orientation.
There are two modes : the GROUNDED and the FREE one. In the free one the translation direction is exactly aligned with the view direction and thus can span the whole set of direction. In the other, the moving direction is constrained to remain horizontal while the view one remains unconstrained. Yet the position is adjusted to stick on the ground (ie upward move are still possible incase of non flat terrain). In both mode the up direction is perpendicular to the moving one.
Unlike most of the other manipulators, the user controls directly the speed of the camera and not its acceleration.
As a result, the user can achieve fast moves and yet change quickly the direction of the movement.
The FirstPerson Manipulator allows the following movements with the listed
Key combinations:
\param SpaceBar Reset the view to the home position.
\param Shift/SpaceBar Reset the up vector to the vertical.
\param UpArrow Run forward.
\param DownArrow Run backward.
\param LeftArrow Step to the left.
\param RightArrow Step to the right.
\param NumPad 0 Jump (only in grounded mode).
\param Shift/UpArrow Move up (only in free mode).
\param Shift/DownArrow Move down (only in free mode).
\param PageDown Switch between GROUNDED and FREE mode.
\param DragMouse Rotate the moving and looking direction.
*/
namespace osgGA {
class OSGGA_EXPORT FirstPersonManipulator : public osgGA::MatrixManipulator
{
public:
/** Default constructor */
FirstPersonManipulator(bool grounded = true);
/** return className
\return returns constant "FirstPerson"
*/
virtual const char* className() const;
/** Set the current position with a matrix
\param matrix A viewpoint matrix.
*/
virtual void setByMatrix( const osg::Matrixd &matrix ) ;
/** Set the current position with the inverse matrix
\param invmat The inverse of a viewpoint matrix
*/
virtual void setByInverseMatrix( const osg::Matrixd &invmat);
/** Get the current viewmatrix */
virtual osg::Matrixd getMatrix() const;
/** Get the current inverse view matrix */
virtual osg::Matrixd getInverseMatrix() const ;
/** Set the subgraph this manipulator is driving the eye through.
\param node root of subgraph
*/
virtual void setNode(osg::Node* 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&) ;
void home(double);
virtual void init(const GUIEventAdapter& ,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;
/** Report the current position as LookAt vectors */
void getCurrentPositionAsLookAt( osg::Vec3 &eye, osg::Vec3 ¢er, osg::Vec3 &up );
void setMinHeight( double in_min_height ) { _minHeightAboveGround = in_min_height; }
double getMinHeight() const { return _minHeightAboveGround; }
void setMinDistance( double in_min_dist ) { _minDistanceInFront = in_min_dist; }
double getMinDistance() const { return _minDistanceInFront; }
void setForwardSpeed( double in_fs ) { _forwardSpeed = in_fs; }
double getForwardSpeed() const { return _forwardSpeed; }
void setSideSpeed( double in_ss ) { _sideSpeed = in_ss; }
double getSideSpeed() const { return _sideSpeed; }
protected:
virtual ~FirstPersonManipulator();
bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const;
bool intersectDownWard(osg::Vec3d& intersection, bool atNodeCenter=true);
osg::ref_ptr<osg::Node> _node;
osg::Matrixd _matrix;
osg::Matrixd _inverseMatrix;
double _minHeightAboveGround;
double _minDistanceInFront;
double _minDistanceAside;
double _speedEpsilon;
double _maxSpeed;
double _forwardSpeed;
double _sideSpeed;
double _upSpeed;
double _speedAccelerationFactor;
double _speedDecelerationFactor;
bool _decelerateSideRate;
bool _decelerateForwardRate;
bool _decelerateUpRate;
double _t0;
double _dt;
osg::Vec3d _forwardDirection;
osg::Vec3d _viewDirection;
osg::Vec3d _upwardDirection;
osg::Vec3d _sideDirection;
double _x;
double _y;
osg::Vec3d _position;
bool _shift;
bool _jump;
bool _grounded;
void _stop();
void _keyDown( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &);
void _keyUp( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter &);
bool _drag( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter &);
void _frame(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter &);
void _adjustPosition();
};
}
#endif
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 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.
*/
/* Written by Don Burns */
#include "../inc/FirstPersonManipulator.h"
#include <osgUtil/LineSegmentIntersector>
#include <osg/io_utils>
#ifndef M_PI
# define M_PI 3.14159265358979323846 /* pi */
#endif
using namespace osgGA;
FirstPersonManipulator::FirstPersonManipulator(bool grounded):
_t0(0.0),
_shift(false),
_jump(false),
_grounded(grounded)
{
_minHeightAboveGround = 2.0;
_minDistanceAside = 1.0;
_minDistanceInFront = 5.0;
_speedAccelerationFactor = 9;
_speedDecelerationFactor = 0.40;
_maxSpeed = 15;
_speedEpsilon = 0.02;
_forwardDirection.set( 0,1,0);
_viewDirection.set( 0,1,0);
_upwardDirection.set( 0,0,1);
_homeUp = _upwardDirection;
_x=0;
_y=0;
_stop();
home(0);
}
FirstPersonManipulator::~FirstPersonManipulator()
{
}
bool FirstPersonManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const
{
osg::ref_ptr<osgUtil::LineSegmentIntersector> lsi = new osgUtil::LineSegmentIntersector(start,end);
osgUtil::IntersectionVisitor iv(lsi.get());
iv.setTraversalMask(_intersectTraversalMask);
_node->accept(iv);
if (lsi->containsIntersections())
{
intersection = lsi->getIntersections().begin()->getWorldIntersectPoint();
return true;
}
return false;
}
void FirstPersonManipulator::setNode( osg::Node *node )
{
_node = node;
if (getAutoComputeHomePosition())
computeHomePosition();
home(0.0);
}
const osg::Node* FirstPersonManipulator::getNode() const
{
return _node.get();
}
osg::Node* FirstPersonManipulator::getNode()
{
return _node.get();
}
const char* FirstPersonManipulator::className() const
{
return "FirstPerson";
}
void FirstPersonManipulator::setByMatrix( const osg::Matrixd &mat )
{
_inverseMatrix = mat;
_matrix.invert( _inverseMatrix );
_position.set( _inverseMatrix(3,0), _inverseMatrix(3,1), _inverseMatrix(3,2 ));
osg::Matrix R(_inverseMatrix);
R(3,0) = R(3,1) = R(3,2) = 0.0;
_forwardDirection = osg::Vec3(0,0,-1) * R; // camera up is +Z, regardless of CoordinateFrame
_viewDirection =_forwardDirection;
_stop();
}
void FirstPersonManipulator::setByInverseMatrix( const osg::Matrixd &invmat)
{
_matrix = invmat;
_inverseMatrix.invert( _matrix );
_position.set( _inverseMatrix(3,0), _inverseMatrix(3,1), _inverseMatrix(3,2 ));
osg::Matrix R(_inverseMatrix);
R(3,0) = R(3,1) = R(3,2) = 0.0;
_forwardDirection = osg::Vec3(0,0,-1) * R; // camera up is +Z, regardless of CoordinateFrame
_viewDirection =_forwardDirection;
_stop();
}
osg::Matrixd FirstPersonManipulator::getMatrix() const
{
return (_matrix);
}
osg::Matrixd FirstPersonManipulator::getInverseMatrix() const
{
return (_inverseMatrix );
}
bool
FirstPersonManipulator::intersectDownWard(osg::Vec3d& intersection, bool atNodeCenter)
{
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. */
osg::Vec3 center= _position;
if(atNodeCenter)
center = bs.center();
else
center[2]=bs.center()[2];
osg::Vec3 A = center + (_upwardDirection*(bs.radius()*2));
osg::Vec3 B = center + (-_upwardDirection*(bs.radius()*2));
if( (B-A).length() == 0.0)
{
return false;
}
// start with it high
double ground = bs.radius() * 3;
osg::Vec3d ip;
if (intersect(A, B, ip))
{
double d = ip*_upwardDirection;
if( d < ground )
ground = d;
}
else
{
//osg::notify(osg::WARN)<<"FirstPersonManipulator : I can't find the ground!"<<std::endl;
ground = 0.0;
return false;
}
intersection = osg::Vec3d(center + _upwardDirection*( ground + _minHeightAboveGround ) );
return true;
}
void FirstPersonManipulator::computeHomePosition()
{
_forwardDirection=osg::Vec3(1,0,0)*(osg::Vec3(1,0,0)*_forwardDirection)+osg::Vec3(0,1,0)*(osg::Vec3(0,1,0)*_forwardDirection);
_forwardDirection.normalize();
_viewDirection =_forwardDirection;
_upwardDirection=_homeUp;
if( !_node.valid() )
return;
osg::Vec3d p;
intersectDownWard(p);
setHomePosition( p, p + _forwardDirection, _homeUp );
}
void FirstPersonManipulator::init(const GUIEventAdapter&, GUIActionAdapter&)
{
//home(ea.getTime());
_stop();
}
void FirstPersonManipulator::home(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
{
home(ea.getTime());
us.requestRedraw();
us.requestContinuousUpdate(false);
}
void FirstPersonManipulator::home(double)
{
if (getAutoComputeHomePosition())
computeHomePosition();
_position = _homeEye;
_forwardDirection=osg::Vec3(1,0,0)*(osg::Vec3(1,0,0)*_forwardDirection)+osg::Vec3(0,1,0)*(osg::Vec3(0,1,0)*_forwardDirection);
_forwardDirection.normalize();
_viewDirection =_forwardDirection;
_upwardDirection=_homeUp;
_inverseMatrix.makeLookAt( _position, _position+_viewDirection, _homeUp );
_matrix.invert( _inverseMatrix );
_forwardSpeed = 0.0;
_sideSpeed = 0.0;
_upSpeed = 0.0;
}
bool FirstPersonManipulator::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter &aa)
{
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::FRAME):
_frame(ea,aa);
return false;
default:
break;
}
if (ea.getHandled()) return false;
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::KEYUP):
_keyUp( ea, aa );
return false;
break;
case(osgGA::GUIEventAdapter::KEYDOWN):
_keyDown(ea, aa);
return false;
break;
case(osgGA::GUIEventAdapter::PUSH):
_x = ea.getXnormalized();
_y = ea.getYnormalized();
return false;
break;
case(osgGA::GUIEventAdapter::DRAG):
aa.requestContinuousUpdate(true);
if(_drag(ea, aa))
aa.requestRedraw();
return false;
break;
case(osgGA::GUIEventAdapter::FRAME):
_frame(ea,aa);
return false;
break;
default:
return false;
}
}
void FirstPersonManipulator::getUsage(osg::ApplicationUsage& usage) const
{
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <SpaceBar>", "Reset the view to the home position.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <Shift/SpaceBar>", "Reset the up vector to the vertical.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <UpArrow>", "Run forward.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <DownArrow>", "Run backward.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <LeftArrow>", "Step to the left.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <RightArrow>", "Step to the right.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <Shift/UpArrow>", "Move up.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <Shift/DownArrow>", "Move down.");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <PageDown>", "Switch Mode(Free vs Grounded).");
usage.addKeyboardMouseBinding("FirstPerson Manipulator: <DragMouse>", "Rotate the moving and looking direction.");
}
void FirstPersonManipulator::_keyUp( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter & )
{
switch( ea.getKey() )
{
case osgGA::GUIEventAdapter::KEY_Up:
case osgGA::GUIEventAdapter::KEY_Down:
if(!_shift)
_decelerateForwardRate = true;
else
_decelerateUpRate = true;
break;
case osgGA::GUIEventAdapter::KEY_Shift_L:
case osgGA::GUIEventAdapter::KEY_Shift_R:
_shift = false;
_decelerateUpRate = true;
break;
case osgGA::GUIEventAdapter::KEY_Left:
case osgGA::GUIEventAdapter::KEY_Right:
if(!_shift)
_decelerateSideRate = true;
break;
}
}
void FirstPersonManipulator::_keyDown( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter & )
{
switch( ea.getKey() )
{
case osgGA::GUIEventAdapter::KEY_Shift_L :
case osgGA::GUIEventAdapter::KEY_Shift_R :
_shift = true;
break;
case osgGA::GUIEventAdapter::KEY_Up:
if( _shift )
{
if(_grounded)
break;
_decelerateForwardRate = true;
if(_upSpeed<_maxSpeed)
_upSpeed += _speedAccelerationFactor;
_decelerateUpRate = false;
}
else
{
if(_forwardSpeed<_maxSpeed*2.f)
_forwardSpeed += _speedAccelerationFactor;
_decelerateForwardRate = false;
}
break;
case osgGA::GUIEventAdapter::KEY_Down:
if( _shift )
{
if(_grounded)
break;
_decelerateForwardRate = true;
if(_upSpeed>-_maxSpeed)
_upSpeed -= _speedAccelerationFactor;
_decelerateUpRate = false;
}
else
{
if(_forwardSpeed>-_maxSpeed*2.f)
_forwardSpeed -= _speedAccelerationFactor;
_decelerateForwardRate = false;
}
break;
case osgGA::GUIEventAdapter::KEY_Right:
if( _shift )
{
_decelerateSideRate = true;
}
else
{
if(_sideSpeed<_maxSpeed)
_sideSpeed += _speedAccelerationFactor;
_decelerateSideRate = false;
}
break;
case osgGA::GUIEventAdapter::KEY_Left:
if( _shift )
{
_decelerateSideRate = true;
}
else
{
if(_sideSpeed>-_maxSpeed)
_sideSpeed -= _speedAccelerationFactor;
_decelerateSideRate = false;
}
break;
case '0':
if(_grounded)
{
if(!_jump)
{
_jump=true;
_upSpeed=_maxSpeed;
}
}
break;
case osgGA::GUIEventAdapter::KEY_Page_Down:
_grounded=!_grounded;
if(_grounded)
{
std::cout<<"Current Mode : GROUNDED"<<std::endl;
osg::Vec3d position=_position;
osg::Vec3d viewDirection=_viewDirection;
home(ea.getTime());
_position=position;
_viewDirection=viewDirection;
}
else
std::cout<<"Current Mode : FREE"<<std::endl;
break;
case ' ':
if(_shift)
{
osg::Vec3d position=_position;
home(ea.getTime());
_position=position;
}
else
{
home(ea.getTime());
}
break;
}
}
bool FirstPersonManipulator::_drag( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter & )
{
float dx = (ea.getXnormalized()-_x)*100.f;
float dy = (ea.getYnormalized()-_y)*40.f; // less sensitivity
_x = ea.getXnormalized();
_y = ea.getYnormalized();
osg::Vec3d up = _upwardDirection;
osg::Vec3d sv = _forwardDirection;
osg::Vec3d lv = sv^up;
lv.normalize();
double yaw = osg::inDegrees(dx*50.0f*_dt);
double pitch = osg::inDegrees(dy*50.0f*_dt);
osg::Quat delta_rotate;
osg::Quat pitch_rotate;
osg::Quat yaw_rotate;
yaw_rotate.makeRotate(-yaw,up.x(),up.y(),up.z());
pitch_rotate.makeRotate(pitch,lv.x(),lv.y(),lv.z());
delta_rotate = yaw_rotate*pitch_rotate;
// update the direction
osg::Matrixd rotation_matrix;
if(_grounded)
{
rotation_matrix.makeRotate(yaw_rotate);
_forwardDirection = _forwardDirection * rotation_matrix;
}
else
{
rotation_matrix.makeRotate(delta_rotate);
_upwardDirection = _upwardDirection * rotation_matrix;
_forwardDirection = _forwardDirection * rotation_matrix;
}
rotation_matrix.makeRotate(delta_rotate);
_viewDirection = _viewDirection * rotation_matrix;
return true;
}
void FirstPersonManipulator::_frame( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter & )
{
double t1 = ea.getTime();
if( _t0 == 0.0 )
{
_t0 = ea.getTime();
_dt = 0.0;
}
else
{
_dt = t1 - _t0;
_t0 = t1;
}
_sideDirection = _forwardDirection * osg::Matrix::rotate( -M_PI*0.5, _upwardDirection);
_position += ( (_forwardDirection * _forwardSpeed) +
(_sideDirection * _sideSpeed) +
(_upwardDirection * _upSpeed) )* _dt;
_adjustPosition();
_inverseMatrix.makeLookAt( _position, _position + _viewDirection, _upwardDirection);
_matrix.invert(_inverseMatrix);
if( _decelerateUpRate )
{
_upSpeed *= _speedDecelerationFactor;
}
if( _decelerateSideRate )
{
_sideSpeed *= _speedDecelerationFactor;
}
if( _decelerateForwardRate )
{
_forwardSpeed *= _speedDecelerationFactor;
}
}
void FirstPersonManipulator::_adjustPosition()
{
if( !_node.valid() )
return;
// Forward line segment at 3 times our intersect distance
typedef std::vector<osg::Vec3d> Intersections;
Intersections intersections;
// Check intersects infront.
osg::Vec3d ip;
osg::Vec3d bottomPosition = _position + _upwardDirection*0.8*_minHeightAboveGround;
if (intersect(bottomPosition,
bottomPosition + (_forwardDirection * (_minDistanceInFront * 1.1f)),
ip ))
{
double d = (ip - bottomPosition).length();
if( d < _minDistanceInFront )
{
_position += _forwardDirection*(d - _minDistanceInFront);
if(_forwardSpeed<0)
_forwardSpeed=0;
}
}
// Check intersects behind.
if (intersect(bottomPosition,
bottomPosition - (_forwardDirection * (_minDistanceInFront * 1.1f)),
ip ))
{
double d = (ip - bottomPosition).length();
if( d < _minDistanceInFront )
{
_position -= _forwardDirection*(d - _minDistanceInFront);
if(_forwardSpeed>0)
_forwardSpeed=0;
}
}
// Check intersects right.
if (intersect(bottomPosition,
bottomPosition + (_sideDirection * (_minDistanceAside * 1.f)),
ip ))
{
double d = (ip - bottomPosition).length();
if( d < _minDistanceAside )
{
_position += _sideDirection*(d - _minDistanceAside);
if(_sideSpeed<0)
_sideSpeed=0;
}
}
// Check intersects left.
if (intersect(bottomPosition,
bottomPosition - (_sideDirection * (_minDistanceAside * 1.f)),
ip ))
{
double d = (ip - bottomPosition).length();
if( d < _minDistanceAside )
{
_position -= _sideDirection*(d - _minDistanceAside);
if(_sideSpeed>0)
_sideSpeed=0;
}
}
// Check intersects below.
if (intersect(_position,
_position - _upwardDirection*_minHeightAboveGround*2.f,
ip ))
{
double d = (ip - _position).length();
if( d <= _minHeightAboveGround*1 )
{
_position = ip + (_upwardDirection * _minHeightAboveGround);
if(_grounded)
{
_upSpeed = 0;
_jump=false;
}
}
else if(_grounded) // stick to the ground
{
_decelerateUpRate = false;
_upSpeed-=_speedDecelerationFactor;
}
}
else if(_grounded)
{
osg::Vec3d pos;
bool aboveGround=false;
if(intersectDownWard(pos,false))
{
double d=(_position-pos)*_upwardDirection;
if(d>0)
{
_decelerateUpRate = false;
_upSpeed-=_speedDecelerationFactor;//gravity effect
aboveGround=true;
}
}
if(!aboveGround)
{
std::cout<<"you are lost in space!"<<std::endl;
home(0);
}
}
// Check intersects above.
if (intersect(_position,
_position + _upwardDirection*_minHeightAboveGround*1.f,
ip ))
{
double d = (ip - _position).length();
if( d < _minHeightAboveGround )
_position = ip - (_upwardDirection * _minHeightAboveGround);
}
}
void FirstPersonManipulator::_stop()
{
_forwardSpeed = 0.0;
_sideSpeed = 0.0;
_upSpeed = 0.0;
}
void FirstPersonManipulator::getCurrentPositionAsLookAt( osg::Vec3 &eye, osg::Vec3 ¢er, osg::Vec3 &up )
{
eye = _position;
center = _position + _viewDirection;
up.set(getUpVector(getCoordinateFrame(_position)));
}
_______________________________________________
osg-users mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org