From Chuck Seberino, "Here is a fix for the RotateCylinderDragger. This patch fixes the case where the picking direction is close to the cylinder axis. The current behavior is this:

* If the eyepoint and cylinder axis are close to parallel (given some tolerance), then it uses a plane perpendicular to the cylinder axis.
* Otherwise it uses a plane parallel to the cylinder axis oriented towards the eyepoint (previous behavior).  This gives decent behavior and is the only path that was taken in the previous code.   I kept with previous behavior and that allowed a good bit of code to be removed, simplifying things.  There is now no need for the _onCylinder flag, but since there is a public accessor, I wasn't sure how to handle it for backwards compatibility, so I left it in.  NOTE - there is no default initialized value, so if it is kept in, it should be set to 'false' to keep same behavior as before.  I am not quite sure how the _onCylinder case was supposed to behave as even forcing that path gave undesirable behavior, even with carefully controlled dragging.
"
This commit is contained in:
Robert Osfield 2012-03-06 10:08:49 +00:00
parent 8d3790d594
commit fd97a9a800
2 changed files with 52 additions and 98 deletions

View File

@ -295,6 +295,7 @@ class OSGMANIPULATOR_EXPORT CylinderPlaneProjector : public CylinderProjector
mutable osg::Plane _plane; mutable osg::Plane _plane;
mutable bool _onCylinder; mutable bool _onCylinder;
mutable osg::Vec3d _planeLineStart, _planeLineEnd; mutable osg::Vec3d _planeLineStart, _planeLineEnd;
mutable bool _parallelPlane;
}; };
} }

View File

@ -16,6 +16,11 @@
using namespace osgManipulator; using namespace osgManipulator;
// When the squared magnitude (length2) of the cross product of 2
// angles is less than this tolerance, they are considered parallel.
// osg::Vec3 a, b; (a ^ b).length2()
#define CROSS_PRODUCT_ANGLE_TOLERANCE 1.0e-1
namespace namespace
{ {
@ -194,27 +199,45 @@ osg::Plane computePlaneThruPointAndOrientedToEye(const osg::Vec3d& eyeDir, const
return plane; return plane;
} }
osg::Plane computePlaneParallelToAxisAndOrientedToEye(const osg::Vec3d& eyeDir, const osg::Matrix& localToWorld, // Computes a plane to be used as a basis for determining a displacement. When eyeDir is close
const osg::Vec3d& axisDir, double radius, // to the cylinder axis, then the plane will be set to be perpendicular to the cylinder axis.
// Otherwise it will be set to be parallel to the cylinder axis and oriented towards eyeDir.
osg::Plane computeIntersectionPlane(const osg::Vec3d& eyeDir, const osg::Matrix& localToWorld,
const osg::Vec3d& axisDir, const osg::Cylinder& cylinder,
osg::Vec3d& planeLineStart, osg::Vec3d& planeLineEnd, osg::Vec3d& planeLineStart, osg::Vec3d& planeLineEnd,
bool front) bool& parallelPlane, bool front)
{ {
osg::Vec3d perpDir = axisDir ^ getLocalEyeDirection(eyeDir, localToWorld); osg::Plane plane;
osg::Vec3d unitAxisDir = axisDir;
unitAxisDir.normalize();
osg::Vec3d perpDir = unitAxisDir ^ getLocalEyeDirection(eyeDir, localToWorld);
// Check to make sure eye and cylinder axis are not too close
if(perpDir.length2() < CROSS_PRODUCT_ANGLE_TOLERANCE)
{
// Too close, so instead return plane perpendicular to cylinder axis.
plane.set(unitAxisDir, cylinder.getCenter());
parallelPlane = false;
return plane;
}
// Otherwise compute plane along axisDir oriented towards eye
osg::Vec3d planeDir = perpDir ^ axisDir; osg::Vec3d planeDir = perpDir ^ axisDir;
planeDir.normalize(); planeDir.normalize();
if (! front) if (! front)
planeDir = -planeDir; planeDir = -planeDir;
osg::Vec3d planePoint = planeDir * radius + axisDir; osg::Vec3d planePoint = planeDir * cylinder.getRadius() + axisDir;
osg::Plane plane;
plane.set(planeDir, planePoint); plane.set(planeDir, planePoint);
planeLineStart = planePoint; planeLineStart = planePoint;
planeLineEnd = planePoint + axisDir; planeLineEnd = planePoint + axisDir;
parallelPlane = true;
return plane; return plane;
} }
} } // namespace
Projector::Projector() : _worldToLocalDirty(false) Projector::Projector() : _worldToLocalDirty(false)
@ -563,86 +586,20 @@ bool CylinderPlaneProjector::project(const PointerInfo& pi, osg::Vec3d& projecte
objectNearPoint = nearPoint * getWorldToLocal(); objectNearPoint = nearPoint * getWorldToLocal();
objectFarPoint = farPoint * getWorldToLocal(); objectFarPoint = farPoint * getWorldToLocal();
// Find the intersection of the sphere with the line. // Computes either a plane parallel to cylinder axis oriented to the eye or the plane
osg::Vec3d cylIntersection; // perpendicular to the cylinder axis if the eye-cylinder angle is close.
bool hitCylinder = false; _plane = computeIntersectionPlane(pi.getEyeDir(), getLocalToWorld(), _cylinderAxis,
if (_front) *_cylinder, _planeLineStart, _planeLineEnd,
{ _parallelPlane, _front);
osg::Vec3d dontCare;
hitCylinder = getCylinderLineIntersection(*_cylinder, objectNearPoint, objectFarPoint, cylIntersection, dontCare);
}
else
{
osg::Vec3d dontCare;
hitCylinder = getCylinderLineIntersection(*_cylinder, objectNearPoint, objectFarPoint, dontCare, cylIntersection);
}
// Compute plane oriented to the eye.
_plane = computePlaneParallelToAxisAndOrientedToEye(pi.getEyeDir(), getLocalToWorld(), _cylinderAxis,
getCylinder()->getRadius(), _planeLineStart, _planeLineEnd,
_front);
// Find the intersection on the plane.
osg::Vec3d planeIntersection;
getPlaneLineIntersection(_plane.asVec4(), objectNearPoint, objectFarPoint, planeIntersection);
if (hitCylinder)
{
osg::Vec3d projectIntersection;
getPlaneLineIntersection(_plane.asVec4(), cylIntersection, cylIntersection + _plane.getNormal(), projectIntersection);
osg::Vec3d closestPointToCylAxis;
computeClosestPointOnLine(getCylinder()->getCenter(), getCylinder()->getCenter() + _cylinderAxis,
projectIntersection, closestPointToCylAxis);
// Distance from the plane intersection point to the closest point on the cylinder axis.
double dist = (projectIntersection - closestPointToCylAxis).length();
if (dist < getCylinder()->getRadius())
{
if (!hitCylinder) return false;
projectedPoint = cylIntersection;
_onCylinder = true;
}
else
{
projectedPoint = planeIntersection;
_onCylinder = false;
}
}
else
{
projectedPoint = planeIntersection;
_onCylinder = false;
}
// Now find the point of intersection on our newly-calculated plane.
getPlaneLineIntersection(_plane.asVec4(), objectNearPoint, objectFarPoint, projectedPoint);
return true; return true;
} }
osg::Quat CylinderPlaneProjector::getRotation(const osg::Vec3d& p1, bool p1OnCyl, const osg::Vec3d& p2, bool p2OnCyl) const osg::Quat CylinderPlaneProjector::getRotation(const osg::Vec3d& p1, bool p1OnCyl, const osg::Vec3d& p2, bool p2OnCyl) const
{ {
if (p1OnCyl && p2OnCyl) if(_parallelPlane)
{
osg::Vec3d closestPointToCylAxis1, closestPointToCylAxis2;
computeClosestPointOnLine(getCylinder()->getCenter(), getCylinder()->getCenter() + _cylinderAxis * getCylinder()->getHeight(),
p1, closestPointToCylAxis1);
computeClosestPointOnLine(getCylinder()->getCenter(), getCylinder()->getCenter() + _cylinderAxis * getCylinder()->getHeight(),
p2, closestPointToCylAxis2);
osg::Vec3d v1 = p1 - closestPointToCylAxis1;
osg::Vec3d v2 = p2 - closestPointToCylAxis2;
double cosAngle = v1 * v2 / (v1.length() * v2.length());
if (cosAngle > 1.0 || cosAngle < -1.0)
return osg::Quat();
double angle = acosf(cosAngle);
osg::Vec3d rotAxis = v1 ^ v2;
return osg::Quat(angle, rotAxis);
}
else if (!p1OnCyl && !p2OnCyl)
{ {
osg::Vec3d closestPointToPlaneLine1, closestPointToPlaneLine2; osg::Vec3d closestPointToPlaneLine1, closestPointToPlaneLine2;
computeClosestPointOnLine(_planeLineStart, _planeLineEnd, computeClosestPointOnLine(_planeLineStart, _planeLineEnd,
@ -656,6 +613,7 @@ osg::Quat CylinderPlaneProjector::getRotation(const osg::Vec3d& p1, bool p1OnCyl
osg::Vec3d diff = v2 - v1; osg::Vec3d diff = v2 - v1;
double d = diff.length(); double d = diff.length();
// The amount of rotation is inversely proportional to the size of the cylinder
double angle = (getCylinder()->getRadius() == 0.0) ? 0.0 : (d / getCylinder()->getRadius()); double angle = (getCylinder()->getRadius() == 0.0) ? 0.0 : (d / getCylinder()->getRadius());
osg::Vec3d rotAxis = _plane.getNormal() ^ v1; osg::Vec3d rotAxis = _plane.getNormal() ^ v1;
@ -663,25 +621,20 @@ osg::Quat CylinderPlaneProjector::getRotation(const osg::Vec3d& p1, bool p1OnCyl
return osg::Quat(angle, rotAxis); return osg::Quat(angle, rotAxis);
else else
return osg::Quat(-angle, rotAxis); return osg::Quat(-angle, rotAxis);
} }
else else
{ {
osg::Vec3d offCylinderPt = (p1OnCyl) ? p2 : p1; osg::Vec3d v1 = p1 - getCylinder()->getCenter();
osg::Vec3d v2 = p2 - getCylinder()->getCenter();
osg::Vec3d linePtNearest; double cosAngle = v1 * v2 / (v1.length() * v2.length());
computeClosestPointOnLine(_planeLineStart, _planeLineEnd,
offCylinderPt, linePtNearest);
osg::Vec3d dirToOffCylinderPt = offCylinderPt - linePtNearest;
dirToOffCylinderPt.normalize();
osg::Vec3d ptOnCylinder = linePtNearest + dirToOffCylinderPt * getCylinder()->getRadius(); if (cosAngle > 1.0 || cosAngle < -1.0)
return osg::Quat();
if (p1OnCyl) double angle = acosf(cosAngle);
return (getRotation(p1, true, ptOnCylinder, true) * osg::Vec3d rotAxis = v1 ^ v2;
getRotation(ptOnCylinder, false, p2, false));
else return osg::Quat(angle, rotAxis);
return (getRotation(p1, false, ptOnCylinder, false) *
getRotation(ptOnCylinder, true, p2, true));
} }
} }