610 lines
17 KiB
C++
610 lines
17 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.
|
|
*/
|
|
|
|
#include <osgGA/NodeTrackerManipulator>
|
|
#include <osg/Quat>
|
|
#include <osg/Notify>
|
|
#include <osg/Transform>
|
|
#include <osgUtil/IntersectVisitor>
|
|
|
|
using namespace osg;
|
|
using namespace osgGA;
|
|
|
|
|
|
class CollectParentPaths : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
CollectParentPaths() :
|
|
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_PARENTS) {}
|
|
|
|
virtual void apply(osg::Node& node)
|
|
{
|
|
if (node.getNumParents()==0)
|
|
{
|
|
_nodePaths.push_back(getNodePath());
|
|
}
|
|
traverse(node);
|
|
}
|
|
|
|
osg::NodePath _nodePath;
|
|
typedef std::vector< osg::NodePath > NodePathList;
|
|
NodePathList _nodePaths;
|
|
};
|
|
|
|
NodeTrackerManipulator::NodeTrackerManipulator()
|
|
{
|
|
_trackerMode = NODE_CENTER_AND_ROTATION;
|
|
_rotationMode = TRACKBALL;
|
|
|
|
_distance = 1.0;
|
|
|
|
_thrown = false;
|
|
|
|
}
|
|
|
|
|
|
NodeTrackerManipulator::~NodeTrackerManipulator()
|
|
{
|
|
}
|
|
|
|
osg::NodePath NodeTrackerManipulator::getNodePath() const
|
|
{
|
|
osg::NodePath nodePath;
|
|
for(ObserveredNodePath::const_iterator itr = _trackNodePath.begin();
|
|
itr != _trackNodePath.end();
|
|
++itr)
|
|
{
|
|
nodePath.push_back(const_cast<osg::Node*>(itr->get()));
|
|
}
|
|
return nodePath;
|
|
}
|
|
|
|
bool NodeTrackerManipulator::validateNodePath() const
|
|
{
|
|
for(ObserveredNodePath::const_iterator itr = _trackNodePath.begin();
|
|
itr != _trackNodePath.begin();
|
|
++itr)
|
|
{
|
|
if (*itr==0)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Warning: tracked node path has been invalidated by changes in the scene graph."<<std::endl;
|
|
const_cast<ObserveredNodePath&>(_trackNodePath).clear();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void NodeTrackerManipulator::setTrackerMode(TrackerMode mode)
|
|
{
|
|
_trackerMode = mode;
|
|
}
|
|
|
|
void NodeTrackerManipulator::setRotationMode(RotationMode mode)
|
|
{
|
|
_rotationMode = mode;
|
|
if (getAutoComputeHomePosition()) computeHomePosition();
|
|
}
|
|
|
|
void NodeTrackerManipulator::setNode(osg::Node* node)
|
|
{
|
|
_node = node;
|
|
|
|
if (_node.get())
|
|
{
|
|
const osg::BoundingSphere& boundingSphere=_node->getBound();
|
|
const float minimumDistanceScale = 0.001f;
|
|
_minimumDistance = osg::clampBetween(
|
|
boundingSphere._radius * minimumDistanceScale,
|
|
0.00001f,1.0f);
|
|
|
|
osg::notify(osg::INFO)<<"Setting Tracker manipulator _minimumDistance to "<<_minimumDistance<<std::endl;
|
|
}
|
|
if (getAutoComputeHomePosition()) computeHomePosition();
|
|
}
|
|
|
|
void NodeTrackerManipulator::setTrackNode(osg::Node* node)
|
|
{
|
|
if (!node)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"NodeTrackerManipulator::setTrackNode(Node*): Unable to set tracked node due to null Node*"<<std::endl;
|
|
return;
|
|
}
|
|
|
|
CollectParentPaths cpp;
|
|
node->accept(cpp);
|
|
|
|
if (!cpp._nodePaths.empty())
|
|
{
|
|
osg::notify(osg::INFO)<<"NodeTrackerManipulator::setTrackNode(Node*"<<node<<" "<<node->getName()<<"): Path set"<<std::endl;
|
|
_trackNodePath.clear();
|
|
setTrackNodePath( cpp._nodePaths[0] );
|
|
}
|
|
else
|
|
{
|
|
osg::notify(osg::NOTICE)<<"NodeTrackerManipulator::setTrackNode(Node*): Unable to set tracked node due to empty parental path."<<std::endl;
|
|
}
|
|
|
|
osg::notify(osg::INFO)<<"setTrackNode("<<node->getName()<<")"<<std::endl;
|
|
for(unsigned int i=0; i<_trackNodePath.size(); ++i)
|
|
{
|
|
osg::notify(osg::INFO)<<" "<<_trackNodePath[i]->className()<<" '"<<_trackNodePath[i]->getName()<<"'"<<std::endl;
|
|
}
|
|
|
|
}
|
|
|
|
const osg::Node* NodeTrackerManipulator::getNode() const
|
|
{
|
|
return _node.get();
|
|
}
|
|
|
|
|
|
osg::Node* NodeTrackerManipulator::getNode()
|
|
{
|
|
return _node.get();
|
|
}
|
|
|
|
|
|
void NodeTrackerManipulator::home(const GUIEventAdapter& ,GUIActionAdapter& us)
|
|
{
|
|
if (getAutoComputeHomePosition()) computeHomePosition();
|
|
|
|
computePosition(_homeEye, _homeCenter, _homeUp);
|
|
us.requestRedraw();
|
|
}
|
|
|
|
void NodeTrackerManipulator::computeHomePosition()
|
|
{
|
|
osg::Node* node = _trackNodePath.empty() ? getNode() : _trackNodePath.back().get();
|
|
|
|
if(node)
|
|
{
|
|
const osg::BoundingSphere& boundingSphere=node->getBound();
|
|
|
|
setHomePosition(boundingSphere._center+osg::Vec3( 0.0,-3.5f * boundingSphere._radius,0.0f),
|
|
boundingSphere._center,
|
|
osg::Vec3(0.0f,0.0f,1.0f),
|
|
_autoComputeHomePosition);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void NodeTrackerManipulator::init(const GUIEventAdapter& ,GUIActionAdapter& )
|
|
{
|
|
flushMouseEventStack();
|
|
}
|
|
|
|
|
|
void NodeTrackerManipulator::getUsage(osg::ApplicationUsage& usage) const
|
|
{
|
|
usage.addKeyboardMouseBinding("NodeTracker: Space","Reset the viewing position to home");
|
|
usage.addKeyboardMouseBinding("NodeTracker: +","When in stereo, increase the fusion distance");
|
|
usage.addKeyboardMouseBinding("NodeTracker: -","When in stereo, reduce the fusion distance");
|
|
}
|
|
|
|
bool NodeTrackerManipulator::handle(const GUIEventAdapter& ea,GUIActionAdapter& us)
|
|
{
|
|
switch(ea.getEventType())
|
|
{
|
|
case(GUIEventAdapter::PUSH):
|
|
{
|
|
flushMouseEventStack();
|
|
addMouseEvent(ea);
|
|
if (calcMovement()) us.requestRedraw();
|
|
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) addMouseEvent(ea);
|
|
|
|
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):
|
|
{
|
|
addMouseEvent(ea);
|
|
if (calcMovement()) us.requestRedraw();
|
|
us.requestContinuousUpdate(false);
|
|
_thrown = false;
|
|
return true;
|
|
}
|
|
|
|
case(GUIEventAdapter::MOVE):
|
|
{
|
|
return false;
|
|
}
|
|
|
|
case(GUIEventAdapter::KEYDOWN):
|
|
if (ea.getKey()==' ')
|
|
{
|
|
flushMouseEventStack();
|
|
_thrown = false;
|
|
home(ea,us);
|
|
us.requestRedraw();
|
|
us.requestContinuousUpdate(false);
|
|
return true;
|
|
}
|
|
return false;
|
|
case(GUIEventAdapter::FRAME):
|
|
if (_thrown)
|
|
{
|
|
if (calcMovement()) us.requestRedraw();
|
|
}
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool NodeTrackerManipulator::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 NodeTrackerManipulator::flushMouseEventStack()
|
|
{
|
|
_ga_t1 = NULL;
|
|
_ga_t0 = NULL;
|
|
}
|
|
|
|
|
|
void NodeTrackerManipulator::addMouseEvent(const GUIEventAdapter& ea)
|
|
{
|
|
_ga_t1 = _ga_t0;
|
|
_ga_t0 = &ea;
|
|
}
|
|
|
|
void NodeTrackerManipulator::setByMatrix(const osg::Matrixd& matrix)
|
|
{
|
|
osg::Vec3d eye,center,up;
|
|
matrix.getLookAt(eye,center,up,_distance);
|
|
computePosition(eye,center,up);
|
|
}
|
|
|
|
void NodeTrackerManipulator::computeNodeWorldToLocal(osg::Matrixd& worldToLocal) const
|
|
{
|
|
if (validateNodePath())
|
|
{
|
|
worldToLocal = osg::computeWorldToLocal(getNodePath());
|
|
}
|
|
}
|
|
|
|
void NodeTrackerManipulator::computeNodeLocalToWorld(osg::Matrixd& localToWorld) const
|
|
{
|
|
if (validateNodePath())
|
|
{
|
|
localToWorld = osg::computeLocalToWorld(getNodePath());
|
|
}
|
|
|
|
}
|
|
|
|
void NodeTrackerManipulator::computeNodeCenterAndRotation(osg::Vec3d& nodeCenter, osg::Quat& nodeRotation) const
|
|
{
|
|
osg::Matrixd localToWorld, worldToLocal;
|
|
computeNodeLocalToWorld(localToWorld);
|
|
computeNodeWorldToLocal(worldToLocal);
|
|
|
|
if (validateNodePath())
|
|
nodeCenter = osg::Vec3d(_trackNodePath.back()->getBound().center())*localToWorld;
|
|
else
|
|
nodeCenter = osg::Vec3d(0.0f,0.0f,0.0f)*localToWorld;
|
|
|
|
|
|
switch(_trackerMode)
|
|
{
|
|
case(NODE_CENTER_AND_AZIM):
|
|
{
|
|
CoordinateFrame coordinateFrame = getCoordinateFrame(nodeCenter);
|
|
osg::Matrixd localToFrame(localToWorld*osg::Matrixd::inverse(coordinateFrame));
|
|
|
|
double azim = atan2(-localToFrame(0,1),localToFrame(0,0));
|
|
osg::Quat nodeRotationRelToFrame, rotationOfFrame;
|
|
nodeRotationRelToFrame.makeRotate(-azim,0.0,0.0,1.0);
|
|
rotationOfFrame = coordinateFrame.getRotate();
|
|
nodeRotation = nodeRotationRelToFrame*rotationOfFrame;
|
|
break;
|
|
}
|
|
case(NODE_CENTER_AND_ROTATION):
|
|
{
|
|
// scale the matrix to get rid of any scales before we extract the rotation.
|
|
double sx = 1.0/sqrt(localToWorld(0,0)*localToWorld(0,0) + localToWorld(1,0)*localToWorld(1,0) + localToWorld(2,0)*localToWorld(2,0));
|
|
double sy = 1.0/sqrt(localToWorld(0,1)*localToWorld(0,1) + localToWorld(1,1)*localToWorld(1,1) + localToWorld(2,1)*localToWorld(2,1));
|
|
double sz = 1.0/sqrt(localToWorld(0,2)*localToWorld(0,2) + localToWorld(1,2)*localToWorld(1,2) + localToWorld(2,2)*localToWorld(2,2));
|
|
localToWorld = localToWorld*osg::Matrixd::scale(sx,sy,sz);
|
|
|
|
nodeRotation = localToWorld.getRotate();
|
|
break;
|
|
}
|
|
case(NODE_CENTER):
|
|
default:
|
|
{
|
|
CoordinateFrame coordinateFrame = getCoordinateFrame(nodeCenter);
|
|
nodeRotation = coordinateFrame.getRotate();
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
osg::Matrixd NodeTrackerManipulator::getMatrix() const
|
|
{
|
|
osg::Vec3d nodeCenter;
|
|
osg::Quat nodeRotation;
|
|
computeNodeCenterAndRotation(nodeCenter,nodeRotation);
|
|
return osg::Matrixd::translate(0.0,0.0,_distance)*osg::Matrixd::rotate(_rotation)*osg::Matrixd::rotate(nodeRotation)*osg::Matrix::translate(nodeCenter);
|
|
}
|
|
|
|
osg::Matrixd NodeTrackerManipulator::getInverseMatrix() const
|
|
{
|
|
osg::Vec3d nodeCenter;
|
|
osg::Quat nodeRotation;
|
|
computeNodeCenterAndRotation(nodeCenter,nodeRotation);
|
|
return osg::Matrixd::translate(-nodeCenter)*osg::Matrixd::rotate(nodeRotation.inverse())*osg::Matrixd::rotate(_rotation.inverse())*osg::Matrixd::translate(0.0,0.0,-_distance);
|
|
}
|
|
|
|
void NodeTrackerManipulator::computePosition(const osg::Vec3d& eye,const osg::Vec3d& center,const osg::Vec3d& up)
|
|
{
|
|
if (!_node) return;
|
|
|
|
// compute rotation matrix
|
|
osg::Vec3 lv(center-eye);
|
|
_distance = lv.length();
|
|
|
|
osg::Matrixd lookat;
|
|
lookat.makeLookAt(eye,center,up);
|
|
|
|
_rotation = lookat.getRotate().inverse();
|
|
}
|
|
|
|
bool NodeTrackerManipulator::calcMovement()
|
|
{
|
|
// return if less then two events have been added.
|
|
if (_ga_t0.get()==NULL || _ga_t1.get()==NULL) return false;
|
|
|
|
double dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized();
|
|
double dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized();
|
|
|
|
|
|
// return if there is no movement.
|
|
if (dx==0 && dy==0) return false;
|
|
|
|
|
|
osg::Vec3d nodeCenter;
|
|
osg::Quat nodeRotation;
|
|
computeNodeCenterAndRotation(nodeCenter, nodeRotation);
|
|
|
|
unsigned int buttonMask = _ga_t1->getButtonMask();
|
|
|
|
if (buttonMask==GUIEventAdapter::LEFT_MOUSE_BUTTON)
|
|
{
|
|
|
|
if (_rotationMode==TRACKBALL)
|
|
{
|
|
|
|
// rotate camera.
|
|
osg::Vec3 axis;
|
|
double angle;
|
|
|
|
double px0 = _ga_t0->getXnormalized();
|
|
double py0 = _ga_t0->getYnormalized();
|
|
|
|
double px1 = _ga_t1->getXnormalized();
|
|
double py1 = _ga_t1->getYnormalized();
|
|
|
|
|
|
trackball(axis,angle,px1,py1,px0,py0);
|
|
|
|
osg::Quat new_rotate;
|
|
new_rotate.makeRotate(angle,axis);
|
|
|
|
_rotation = _rotation*new_rotate;
|
|
}
|
|
else
|
|
{
|
|
osg::Matrix rotation_matrix;
|
|
rotation_matrix.makeRotate(_rotation);
|
|
|
|
osg::Vec3d lookVector = -getUpVector(rotation_matrix);
|
|
osg::Vec3d sideVector = getSideVector(rotation_matrix);
|
|
osg::Vec3d upVector = getFrontVector(rotation_matrix);
|
|
|
|
osg::Vec3d localUp(0.0f,0.0f,1.0f);
|
|
|
|
osg::Vec3d forwardVector = localUp^sideVector;
|
|
sideVector = forwardVector^localUp;
|
|
|
|
forwardVector.normalize();
|
|
sideVector.normalize();
|
|
|
|
osg::Quat rotate_elevation;
|
|
rotate_elevation.makeRotate(dy,sideVector);
|
|
|
|
osg::Quat rotate_azim;
|
|
rotate_azim.makeRotate(-dx,localUp);
|
|
|
|
_rotation = _rotation * rotate_elevation * rotate_azim;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
else if (buttonMask==GUIEventAdapter::MIDDLE_MOUSE_BUTTON ||
|
|
buttonMask==(GUIEventAdapter::LEFT_MOUSE_BUTTON|GUIEventAdapter::RIGHT_MOUSE_BUTTON))
|
|
{
|
|
return true;
|
|
}
|
|
else if (buttonMask==GUIEventAdapter::RIGHT_MOUSE_BUTTON)
|
|
{
|
|
|
|
// zoom model.
|
|
|
|
double fd = _distance;
|
|
double scale = 1.0f+dy;
|
|
if (fd*scale>_minimumDistance)
|
|
{
|
|
|
|
_distance *= scale;
|
|
|
|
} else
|
|
{
|
|
_distance = _minimumDistance;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void NodeTrackerManipulator::clampOrientation()
|
|
{
|
|
}
|
|
|
|
|
|
/*
|
|
* This size should really be based on the distance from the center of
|
|
* rotation to the point on the object underneath the mouse. That
|
|
* point would then track the mouse as closely as possible. This is a
|
|
* simple example, though, so that is left as an Exercise for the
|
|
* Programmer.
|
|
*/
|
|
const float TRACKBALLSIZE = 0.8f;
|
|
|
|
/*
|
|
* Ok, simulate a track-ball. Project the points onto the virtual
|
|
* trackball, then figure out the axis of rotation, which is the cross
|
|
* product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
|
|
* Note: This is a deformed trackball-- is a trackball in the center,
|
|
* but is deformed into a hyperbolic sheet of rotation away from the
|
|
* center. This particular function was chosen after trying out
|
|
* several variations.
|
|
*
|
|
* It is assumed that the arguments to this routine are in the range
|
|
* (-1.0 ... 1.0)
|
|
*/
|
|
void NodeTrackerManipulator::trackball(osg::Vec3& axis,double & angle, double p1x, double p1y, double p2x, double p2y)
|
|
{
|
|
/*
|
|
* First, figure out z-coordinates for projection of P1 and P2 to
|
|
* deformed sphere
|
|
*/
|
|
|
|
osg::Matrix rotation_matrix(_rotation);
|
|
|
|
|
|
osg::Vec3d uv = osg::Vec3d(0.0,1.0,0.0)*rotation_matrix;
|
|
osg::Vec3d sv = osg::Vec3d(1.0,0.0,0.0)*rotation_matrix;
|
|
osg::Vec3d lv = osg::Vec3d(0.0,0.0,-1.0)*rotation_matrix;
|
|
|
|
osg::Vec3d p1 = sv*p1x+uv*p1y-lv*tb_project_to_sphere(TRACKBALLSIZE,p1x,p1y);
|
|
osg::Vec3d p2 = sv*p2x+uv*p2y-lv*tb_project_to_sphere(TRACKBALLSIZE,p2x,p2y);
|
|
|
|
/*
|
|
* Now, we want the cross product of P1 and P2
|
|
*/
|
|
|
|
// Robert,
|
|
//
|
|
// This was the quick 'n' dirty fix to get the trackball doing the right
|
|
// thing after fixing the Quat rotations to be right-handed. You may want
|
|
// to do something more elegant.
|
|
// axis = p1^p2;
|
|
axis = p2^p1;
|
|
axis.normalize();
|
|
|
|
/*
|
|
* Figure out how much to rotate around that axis.
|
|
*/
|
|
double t = (p2-p1).length() / (2.0*TRACKBALLSIZE);
|
|
|
|
/*
|
|
* Avoid problems with out-of-control values...
|
|
*/
|
|
if (t > 1.0) t = 1.0;
|
|
if (t < -1.0) t = -1.0;
|
|
angle = inRadians(asin(t));
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
|
|
* if we are away from the center of the sphere.
|
|
*/
|
|
double NodeTrackerManipulator::tb_project_to_sphere(double r, double x, double y)
|
|
{
|
|
float d, t, z;
|
|
|
|
d = sqrt(x*x + y*y);
|
|
/* Inside sphere */
|
|
if (d < r * 0.70710678118654752440)
|
|
{
|
|
z = sqrt(r*r - d*d);
|
|
} /* On hyperbola */
|
|
else
|
|
{
|
|
t = r / 1.41421356237309504880;
|
|
z = t*t / d;
|
|
}
|
|
return z;
|
|
}
|