Files
OpenSceneGraph/src/osgGA/DriveManipulator.cpp
Robert Osfield 06abd75198 From Mathias Froehlich, "We are currently getting issues with locale settings and some osg plugins.
Therefore I have changed all the occurances of atof by asciiToFloat or
asciiToDouble.

I believe that it is safe to do so at least for all the plugins.
Included here are also asciiToFloat conversion of environment variables. One
might argue that these should be locale dependent. But IMO these should be
set and interpreted by osg independent of the current locale.
"
2009-06-25 16:07:49 +00:00

607 lines
15 KiB
C++

/* -*-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.
*/
#if defined(_MSC_VER)
#pragma warning( disable : 4786 )
#endif
#include <stdlib.h>
#include <osgGA/DriveManipulator>
#include <osgUtil/LineSegmentIntersector>
#include <osg/Notify>
using namespace osg;
using namespace osgGA;
#define DRIVER_HEIGHT 15
// #define ABOSULTE_PITCH 1
// #define INCREMENTAL_PITCH 1
#define KEYBOARD_PITCH 1
static double getHeightOfDriver()
{
double height = 1.5;
if (getenv("OSG_DRIVE_MANIPULATOR_HEIGHT"))
{
height = osg::asciiToDouble(getenv("OSG_DRIVE_MANIPULATOR_HEIGHT"));
}
osg::notify(osg::INFO)<<"DriveManipulator::_height set to =="<<height<<std::endl;
return height;
}
DriveManipulator::DriveManipulator()
{
_modelScale = 1.0;
_velocity = 0.0;
_height = getHeightOfDriver();
_buffer = _height*2.5;
_pitch = 0.0;
//_speedMode = USE_MOUSE_Y_FOR_SPEED;
_speedMode = USE_MOUSE_BUTTONS_FOR_SPEED;
_pitchUpKeyPressed = false;
_pitchDownKeyPressed = false;
}
DriveManipulator::~DriveManipulator()
{
}
void DriveManipulator::setNode(osg::Node* node)
{
_node = node;
if (_node.get())
{
const osg::BoundingSphere& boundingSphere=_node->getBound();
_modelScale = boundingSphere._radius;
//_height = sqrtf(_modelScale)*0.03;
//_buffer = sqrtf(_modelScale)*0.05;
_height = getHeightOfDriver();
_buffer = _height*2.5;
}
if (getAutoComputeHomePosition()) computeHomePosition();
}
const osg::Node* DriveManipulator::getNode() const
{
return _node.get();
}
osg::Node* DriveManipulator::getNode()
{
return _node.get();
}
bool DriveManipulator::intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection, osg::Vec3d& normal) 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();
normal = lsi->getIntersections().begin()->getWorldIntersectNormal();
return true;
}
return false;
}
void DriveManipulator::computeHomePosition()
{
if(_node.get())
{
const osg::BoundingSphere& boundingSphere=_node->getBound();
osg::Vec3d ep = boundingSphere._center;
osg::Vec3d bp = ep;
osg::CoordinateFrame cf=getCoordinateFrame(ep);
ep -= getUpVector(cf)* _modelScale*0.0001;
bp -= getUpVector(cf)* _modelScale;
// check to see if any obstruction in front.
bool positionSet = false;
osg::Vec3d ip, np;
if (intersect(ep, bp, ip, np))
{
osg::Vec3d uv;
if (np * getUpVector(cf)>0.0) uv = np;
else uv = -np;
ep = ip;
ep += getUpVector(cf)*_height;
osg::Vec3 lv = uv^osg::Vec3d(1.0,0.0,0.0);
setHomePosition(ep,ep+lv,uv);
positionSet = true;
}
if (!positionSet)
{
bp = ep;
bp += getUpVector(cf)*_modelScale;
if (intersect(ep, bp, ip, np))
{
osg::Vec3d uv;
if (np*getUpVector(cf)>0.0) uv = np;
else uv = -np;
ep = ip;
ep += getUpVector(cf)*_height;
osg::Vec3 lv = uv^osg::Vec3d(1.0,0.0,0.0);
setHomePosition(ep,ep+lv,uv);
positionSet = true;
}
}
if (!positionSet)
{
setHomePosition(
boundingSphere._center+osg::Vec3d( 0.0,-2.0 * boundingSphere._radius,0.0),
boundingSphere._center+osg::Vec3d( 0.0,-2.0 * boundingSphere._radius,0.0)+osg::Vec3d(0.0,1.0,0.0),
osg::Vec3d(0.0,0.0,1.0));
}
}
}
void DriveManipulator::home(const GUIEventAdapter& ea,GUIActionAdapter& us)
{
if (getAutoComputeHomePosition()) computeHomePosition();
computePosition(_homeEye, _homeCenter, _homeUp);
_velocity = 0.0;
_pitch = 0.0;
us.requestRedraw();
us.requestContinuousUpdate(false);
us.requestWarpPointer((ea.getXmin()+ea.getXmax())/2.0f,(ea.getYmin()+ea.getYmax())/2.0f);
flushMouseEventStack();
}
void DriveManipulator::init(const GUIEventAdapter& ea,GUIActionAdapter& us)
{
flushMouseEventStack();
us.requestContinuousUpdate(false);
_velocity = 0.0;
osg::Vec3d ep = _eye;
osg::CoordinateFrame cf=getCoordinateFrame(ep);
Matrixd rotation_matrix;
rotation_matrix.makeRotate(_rotation);
osg::Vec3d sv = osg::Vec3d(1.0,0.0,0.0) * rotation_matrix;
osg::Vec3d bp = ep;
bp -= getUpVector(cf)*_modelScale;
bool positionSet = false;
osg::Vec3d ip, np;
if (intersect(ep, bp, ip, np))
{
osg::Vec3d uv;
if (np*getUpVector(cf)>0.0) uv = np;
else uv = -np;
ep = ip+uv*_height;
osg::Vec3d lv = uv^sv;
computePosition(ep,ep+lv,uv);
positionSet = true;
}
if (!positionSet)
{
bp = ep;
bp += getUpVector(cf)*_modelScale;
if (intersect(ep, bp, ip, np))
{
osg::Vec3d uv;
if (np*getUpVector(cf)>0.0f) uv = np;
else uv = -np;
ep = ip+uv*_height;
osg::Vec3 lv = uv^sv;
computePosition(ep,ep+lv,uv);
positionSet = true;
}
}
if (ea.getEventType()!=GUIEventAdapter::RESIZE)
{
us.requestWarpPointer((ea.getXmin()+ea.getXmax())/2.0f,(ea.getYmin()+ea.getYmax())/2.0f);
}
}
bool DriveManipulator::handle(const GUIEventAdapter& ea,GUIActionAdapter& us)
{
switch(ea.getEventType())
{
case(GUIEventAdapter::FRAME):
addMouseEvent(ea);
if (calcMovement()) us.requestRedraw();
return false;
case(GUIEventAdapter::RESIZE):
init(ea,us);
us.requestRedraw();
return true;
default:
break;
}
if (ea.getHandled()) return false;
switch(ea.getEventType())
{
case(GUIEventAdapter::PUSH):
{
addMouseEvent(ea);
us.requestContinuousUpdate(true);
if (calcMovement()) us.requestRedraw();
return true;
}
case(GUIEventAdapter::RELEASE):
{
addMouseEvent(ea);
us.requestContinuousUpdate(true);
if (calcMovement()) us.requestRedraw();
return true;
}
case(GUIEventAdapter::DRAG):
{
addMouseEvent(ea);
us.requestContinuousUpdate(true);
if (calcMovement()) us.requestRedraw();
return true;
}
case(GUIEventAdapter::MOVE):
{
addMouseEvent(ea);
us.requestContinuousUpdate(true);
if (calcMovement()) us.requestRedraw();
return true;
}
case(GUIEventAdapter::KEYDOWN):
{
if (ea.getKey()==GUIEventAdapter::KEY_Space)
{
flushMouseEventStack();
home(ea,us);
return true;
}
else if (ea.getKey()=='q')
{
_speedMode = USE_MOUSE_Y_FOR_SPEED;
return true;
}
else if (ea.getKey()=='a')
{
_speedMode = USE_MOUSE_BUTTONS_FOR_SPEED;
return true;
}
#ifdef KEYBOARD_PITCH
else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_Up ||
ea.getKey()==osgGA::GUIEventAdapter::KEY_KP_Up ||
ea.getKey()=='9')
{
_pitchUpKeyPressed = true;
return true;
}
else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_Down ||
ea.getKey()==osgGA::GUIEventAdapter::KEY_KP_Down ||
ea.getKey()=='6')
{
_pitchDownKeyPressed = true;
return true;
}
#endif
return false;
}
case(GUIEventAdapter::KEYUP):
{
#ifdef KEYBOARD_PITCH
if (ea.getKey()==osgGA::GUIEventAdapter::KEY_Up ||
ea.getKey()==osgGA::GUIEventAdapter::KEY_KP_Up ||
ea.getKey()=='9')
{
_pitchUpKeyPressed = false;
return true;
}
else if (ea.getKey()==osgGA::GUIEventAdapter::KEY_Down ||
ea.getKey()==osgGA::GUIEventAdapter::KEY_KP_Down ||
ea.getKey()=='6')
{
_pitchDownKeyPressed = false;
return true;
}
#endif
return false;
}
default:
return false;
}
}
void DriveManipulator::getUsage(osg::ApplicationUsage& usage) const
{
usage.addKeyboardMouseBinding("Drive: Space","Reset the viewing position to home");
usage.addKeyboardMouseBinding("Drive: q","Use mouse y for controlling speed");
usage.addKeyboardMouseBinding("Drive: a","Use mouse middle,right mouse buttons for speed");
usage.addKeyboardMouseBinding("Drive: Down","Cursor down key to look downwards");
usage.addKeyboardMouseBinding("Drive: Up","Cursor up key to look upwards");
}
void DriveManipulator::flushMouseEventStack()
{
_ga_t1 = NULL;
_ga_t0 = NULL;
}
void DriveManipulator::addMouseEvent(const GUIEventAdapter& ea)
{
_ga_t1 = _ga_t0;
_ga_t0 = &ea;
}
void DriveManipulator::setByMatrix(const osg::Matrixd& matrix)
{
_eye = matrix.getTrans();
_rotation = matrix.getRotate();
}
osg::Matrixd DriveManipulator::getMatrix() const
{
return osg::Matrixd::rotate(_pitch,1.0,0.0,0.0)*osg::Matrixd::rotate(_rotation)*osg::Matrixd::translate(_eye);
}
osg::Matrixd DriveManipulator::getInverseMatrix() const
{
return osg::Matrixd::translate(-_eye)*osg::Matrixd::rotate(_rotation.inverse())*osg::Matrixd::rotate(-_pitch,1.0,0.0,0.0);
}
void DriveManipulator::computePosition(const osg::Vec3d& eye,const osg::Vec3d& center,const osg::Vec3d& up)
{
osg::Vec3d lv = center-eye;
osg::Vec3d f(lv);
f.normalize();
osg::Vec3d s(f^up);
s.normalize();
osg::Vec3d u(s^f);
u.normalize();
osg::Matrixd rotation_matrix(s[0], u[0], -f[0], 0.0,
s[1], u[1], -f[1], 0.0,
s[2], u[2], -f[2], 0.0,
0.0, 0.0, 0.0, 1.0);
_eye = eye;
_rotation = rotation_matrix.getRotate().inverse();
}
bool DriveManipulator::calcMovement()
{
// return if less then two events have been added.
if (_ga_t0.get()==NULL || _ga_t1.get()==NULL) return false;
double dt = _ga_t0->getTime()-_ga_t1->getTime();
if (dt<0.0f)
{
notify(INFO) << "warning dt = "<<dt<< std::endl;
dt = 0.0;
}
double accelerationFactor = _height*10.0;
switch(_speedMode)
{
case(USE_MOUSE_Y_FOR_SPEED):
{
double dy = _ga_t0->getYnormalized();
_velocity = _height*dy;
break;
}
case(USE_MOUSE_BUTTONS_FOR_SPEED):
{
unsigned int buttonMask = _ga_t1->getButtonMask();
if (buttonMask==GUIEventAdapter::LEFT_MOUSE_BUTTON)
{
// pan model.
_velocity += dt*accelerationFactor;
}
else if (buttonMask==GUIEventAdapter::MIDDLE_MOUSE_BUTTON ||
buttonMask==(GUIEventAdapter::LEFT_MOUSE_BUTTON|GUIEventAdapter::RIGHT_MOUSE_BUTTON))
{
_velocity = 0.0;
}
else if (buttonMask==GUIEventAdapter::RIGHT_MOUSE_BUTTON)
{
_velocity -= dt*accelerationFactor;
}
break;
}
}
osg::CoordinateFrame cf=getCoordinateFrame(_eye);
osg::Matrixd rotation_matrix;
rotation_matrix.makeRotate(_rotation);
osg::Vec3d up = osg::Vec3d(0.0,1.0,0.0) * rotation_matrix;
osg::Vec3d lv = osg::Vec3d(0.0,0.0,-1.0) * rotation_matrix;
osg::Vec3d sv = osg::Vec3d(1.0,0.0,0.0) * rotation_matrix;
// rotate the camera.
double dx = _ga_t0->getXnormalized();
double yaw = -inDegrees(dx*50.0*dt);
#ifdef KEYBOARD_PITCH
double pitch_delta = 0.5;
if (_pitchUpKeyPressed) _pitch += pitch_delta*dt;
if (_pitchDownKeyPressed) _pitch -= pitch_delta*dt;
#endif
#if defined(ABOSULTE_PITCH)
// absolute pitch
double dy = _ga_t0->getYnormalized();
_pitch = -dy*0.5;
#elif defined(INCREMENTAL_PITCH)
// incremental pitch
double dy = _ga_t0->getYnormalized();
_pitch += dy*dt;
#endif
osg::Quat yaw_rotation;
yaw_rotation.makeRotate(yaw,up);
_rotation *= yaw_rotation;
rotation_matrix.makeRotate(_rotation);
sv = osg::Vec3d(1.0,0.0,0.0) * rotation_matrix;
// movement is big enough the move the eye point along the look vector.
if (fabs(_velocity*dt)>1e-8)
{
double distanceToMove = _velocity*dt;
double signedBuffer;
if (distanceToMove>=0.0) signedBuffer=_buffer;
else signedBuffer=-_buffer;
// check to see if any obstruction in front.
osg::Vec3d ip, np;
if (intersect(_eye,_eye+lv*(signedBuffer+distanceToMove), ip, np))
{
if (distanceToMove>=0.0)
{
distanceToMove = (ip-_eye).length()-_buffer;
}
else
{
distanceToMove = _buffer-(ip-_eye).length();
}
_velocity = 0.0;
}
// check to see if forward point is correct height above terrain.
osg::Vec3d fp = _eye + lv*distanceToMove;
osg::Vec3d lfp = fp - up*(_height*5.0);
if (intersect(fp, lfp, ip, np))
{
if (up*np>0.0) up = np;
else up = -np;
_eye = ip+up*_height;
lv = up^sv;
computePosition(_eye,_eye+lv,up);
return true;
}
// no hit on the terrain found therefore resort to a fall under
// under the influence of gravity.
osg::Vec3d dp = lfp;
dp -= getUpVector(cf)* (2.0*_modelScale);
if (intersect(lfp, dp, ip, np))
{
if (up*np>0.0) up = np;
else up = -np;
_eye = ip+up*_height;
lv = up^sv;
computePosition(_eye,_eye+lv,up);
return true;
}
// no collision with terrain has been found therefore track horizontally.
lv *= (_velocity*dt);
_eye += lv;
}
return true;
}