Hi guys,
I was looking for an osg manipulator fitted for walkthrough in architectural
environments. I found that the best ones were UFO, Drive and Flight. Though,
I was not really convinced by any of them as there is no direct control on
the speed but only on the acceleration. Thus, it is hard to change quickly
the direction of the movement.

Based on the UFO maipulator, I've implemented a manipulator closer to the
ones used in 3rd person games. I think it's documented enough and coded in a
simple way and so easy to review. So if some of you find it  promising and
useful, we could improve it and eventually submit it, so as to integrate it
in the osg Core.

Waiting for your suggestions.

Note: you may need to edit DoomLikeManipulator.cpp:16 #include
"../inc/DoomLikeManipulator.h" to get it compile.

-- 
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::DoomLikeManipulator
  \brief A DoomLike manipulator driven with keybindings.

  The DoomLikeManipulator 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 horizontal and the free. 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. Note : horizontal mode is not implemented yet!!
  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 DoomLike 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 Shift/UpArrow    Move up.
    \param Shift/DownArrow  Move down.
    \param Shift/Enter      Switch between horizontal and free mode.
    \param DragMouse        Rotate the moving and looking direction.
*/

namespace osgGA {

class OSGGA_EXPORT DoomLikeManipulator : public osgGA::MatrixManipulator
{

    public:
        /** Default constructor */
        DoomLikeManipulator();

        /** return className
          \return returns constant "DoomLike"
          */
        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 &center, 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 ~DoomLikeManipulator();

        bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const;

        osg::ref_ptr<osg::Node> _node;
        osg::Matrixd _matrix;
        osg::Matrixd _inverseMatrix;

        double    _minHeightAboveGround;
        double    _minDistanceInFront;

        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 _direction;  
        osg::Vec3d _upVector;
        double _x;
        double _y;
        osg::Vec3d _position;


        bool _shift;
        bool _ctrl;

        void _stop();
        void _keyDown( const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &);
        void _keyUp( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter &);
        bool _move( 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/DoomLikeManipulator.h"
#include <osgUtil/LineSegmentIntersector>

#include <osg/io_utils>

#ifndef M_PI
# define M_PI       3.14159265358979323846  /* pi */
#endif

using namespace osgGA;

DoomLikeManipulator::DoomLikeManipulator():
            _t0(0.0),
            _shift(false),
            _ctrl(false)
{ 
    _minHeightAboveGround          = 2.0;
    _minDistanceInFront            = 5.0;

    _speedAccelerationFactor       = 9;
    _speedDecelerationFactor       = 0.40;

    _maxSpeed                      = 15;
    _speedEpsilon                  = 0.02;
    


    _direction.set( 0,1,0);
    _upVector.set( 0,0,1);
    _homeUp = _upVector;
    _x=0;
    _y=0;
    _stop();
}

DoomLikeManipulator::~DoomLikeManipulator()
{
}

bool DoomLikeManipulator::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 DoomLikeManipulator::setNode( osg::Node *node )
{
    _node = node;

    if (getAutoComputeHomePosition()) 
        computeHomePosition();

    home(0.0);
}

const osg::Node* DoomLikeManipulator::getNode() const
{
    return _node.get();
}

osg::Node* DoomLikeManipulator::getNode()
{
    return _node.get();
}


const char* DoomLikeManipulator::className() const 
{ 
    return "DoomLike"; 
}

void DoomLikeManipulator::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;
    _direction = osg::Vec3(0,0,-1) * R; // camera up is +Z, regardless of CoordinateFrame

    _stop();
}

void DoomLikeManipulator::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;
    _direction = osg::Vec3(0,0,-1) * R; // camera up is +Z, regardless of CoordinateFrame

    _stop();
}

osg::Matrixd DoomLikeManipulator::getMatrix() const
{
    return (_matrix);
}

osg::Matrixd DoomLikeManipulator::getInverseMatrix() const 
{
    return (_inverseMatrix );
}

void DoomLikeManipulator::computeHomePosition()
{
    _direction=osg::Vec3(1,0,0)*(osg::Vec3(1,0,0)*_direction)+osg::Vec3(0,1,0)*(osg::Vec3(0,1,0)*_direction);
    _direction.normalize();
    _upVector=_homeUp;
    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. */



    osg::Vec3 A = bs.center() + (_upVector*(bs.radius()*2));
    osg::Vec3 B = bs.center() + (-_upVector*(bs.radius()*2));

    if( (B-A).length() == 0.0)
    {
        return;
    }

    // start with it high
    double ground = bs.radius() * 3;

    osg::Vec3d ip;
    if (intersect(A, B, ip))
    {
        double d = ip.length();
        if( d < ground )
            ground = d;
    }
    else
    {
        //osg::notify(osg::WARN)<<"DoomLikeManipulator : I can't find the ground!"<<std::endl;
        ground = 0.0;
    }


    osg::Vec3 p(bs.center() + _upVector*( ground + _minHeightAboveGround*1.25 ) );
    setHomePosition( p, p + _direction, _homeUp );
}

void DoomLikeManipulator::init(const GUIEventAdapter&, GUIActionAdapter&)
{
    //home(ea.getTime());

    _stop();
}

void DoomLikeManipulator::home(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us) 
{
    home(ea.getTime());
    us.requestRedraw();
    us.requestContinuousUpdate(false);

}

void DoomLikeManipulator::home(double) 
{
    if (getAutoComputeHomePosition()) 
        computeHomePosition();

    _position = _homeEye;
    _direction=osg::Vec3(1,0,0)*(osg::Vec3(1,0,0)*_direction)+osg::Vec3(0,1,0)*(osg::Vec3(0,1,0)*_direction);
    _upVector=_homeUp;
    _direction.normalize();
    
    _inverseMatrix.makeLookAt( _position, _position+_direction, _homeUp );
    _matrix.invert( _inverseMatrix );


    _forwardSpeed = 0.0;
    _sideSpeed = 0.0;
    _upSpeed = 0.0;
}

bool DoomLikeManipulator::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(_move(ea, aa))
                aa.requestRedraw();
            return false;
            break;

        case(osgGA::GUIEventAdapter::FRAME):
            _frame(ea,aa);
            return false;
            break;

        default:
            return false;
    }
}

void DoomLikeManipulator::getUsage(osg::ApplicationUsage& usage) const
{

    usage.addKeyboardMouseBinding("DoomLike Manipulator: <SpaceBar>",        "Reset the view to the home position.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <Shift/SpaceBar>",  "Reset the up vector to the vertical.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <UpArrow>",         "Run forward.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <DownArrow>",       "Run backward.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <LeftArrow>",       "Step to the left.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <RightArrow>",      "Step to the right.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <Shift/UpArrow>",   "Move up.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <Shift/DownArrow>", "Move down.");
    usage.addKeyboardMouseBinding("DoomLike Manipulator: <DragMouse>",       "Rotate the moving and looking direction.");
}



void DoomLikeManipulator::_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 DoomLikeManipulator::_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 )
            {
                _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 )
            {
                _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 ' ':
            if(_shift)
            {
                osg::Vec3d position=_position;
                home(ea.getTime());
                _position=position;
            }
            else
            {
                home(ea.getTime());
            }
            break;
    }

}


bool DoomLikeManipulator::_move( 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 = _upVector;
    osg::Vec3d sv = _direction;
    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;
    rotation_matrix.makeRotate(delta_rotate);
    _upVector = _upVector * rotation_matrix;
    _direction = _direction * rotation_matrix;
    return true;
    
}


void DoomLikeManipulator::_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;
    }

      osg::Vec3d _sideVec = _direction * osg::Matrix::rotate( -M_PI*0.5, _upVector);

      _position += ( (_direction       * _forwardSpeed) + 
                     (_sideVec         * _sideSpeed) +
                     (_upVector * _upSpeed) )* _dt;



    _adjustPosition();

    _inverseMatrix.makeLookAt( _position, _position + _direction, _upVector); 
    _matrix.invert(_inverseMatrix);

    if( _decelerateUpRate )
    {
        _upSpeed   *= _speedDecelerationFactor;
    }
    if( _decelerateSideRate )
    {
        _sideSpeed   *= _speedDecelerationFactor;
    }
    if( _decelerateForwardRate )
    {
        _forwardSpeed   *= _speedDecelerationFactor;
    } 
}

void DoomLikeManipulator::_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;
    if (intersect(_position, 
                  _position + (_direction * (_minDistanceInFront * 3.0)),
                  ip ))
    {
        double d = (ip - _position).length();

        if( d < _minDistanceInFront )
        {
            _position = ip + (_direction * -_minDistanceInFront);
            _stop();
        }
    }
    
    // Check intersects below.

    if (intersect(_position, 
                  _position - _upVector*_minHeightAboveGround*3, 
                  ip ))
    {
        double d = (ip - _position).length();

        if( d < _minHeightAboveGround )
          _position = ip + (_upVector * _minHeightAboveGround);
    }
}


void DoomLikeManipulator::_stop()
{
    _forwardSpeed = 0.0;
    _sideSpeed = 0.0;
    _upSpeed = 0.0;
}

void DoomLikeManipulator::getCurrentPositionAsLookAt( osg::Vec3 &eye, osg::Vec3 &center, osg::Vec3 &up )
{
    eye = _position;
    center = _position + _direction;
    up.set(getUpVector(getCoordinateFrame(_position)));
}

_______________________________________________
osg-users mailing list
osg-users@lists.openscenegraph.org
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Reply via email to