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 bool _onCylinder;
mutable osg::Vec3d _planeLineStart, _planeLineEnd;
mutable bool _parallelPlane;
};
}

View File

@ -16,6 +16,11 @@
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
{
@ -194,27 +199,45 @@ osg::Plane computePlaneThruPointAndOrientedToEye(const osg::Vec3d& eyeDir, const
return plane;
}
osg::Plane computePlaneParallelToAxisAndOrientedToEye(const osg::Vec3d& eyeDir, const osg::Matrix& localToWorld,
const osg::Vec3d& axisDir, double radius,
osg::Vec3d& planeLineStart, osg::Vec3d& planeLineEnd,
bool front)
// Computes a plane to be used as a basis for determining a displacement. When eyeDir is close
// 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,
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;
planeDir.normalize();
if (! front)
planeDir = -planeDir;
osg::Vec3d planePoint = planeDir * radius + axisDir;
osg::Plane plane;
osg::Vec3d planePoint = planeDir * cylinder.getRadius() + axisDir;
plane.set(planeDir, planePoint);
planeLineStart = planePoint;
planeLineEnd = planePoint + axisDir;
parallelPlane = true;
return plane;
}
}
} // namespace
Projector::Projector() : _worldToLocalDirty(false)
@ -563,86 +586,20 @@ bool CylinderPlaneProjector::project(const PointerInfo& pi, osg::Vec3d& projecte
objectNearPoint = nearPoint * getWorldToLocal();
objectFarPoint = farPoint * getWorldToLocal();
// Find the intersection of the sphere with the line.
osg::Vec3d cylIntersection;
bool hitCylinder = false;
if (_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;
}
// Computes either a plane parallel to cylinder axis oriented to the eye or the plane
// perpendicular to the cylinder axis if the eye-cylinder angle is close.
_plane = computeIntersectionPlane(pi.getEyeDir(), getLocalToWorld(), _cylinderAxis,
*_cylinder, _planeLineStart, _planeLineEnd,
_parallelPlane, _front);
// Now find the point of intersection on our newly-calculated plane.
getPlaneLineIntersection(_plane.asVec4(), objectNearPoint, objectFarPoint, projectedPoint);
return true;
}
osg::Quat CylinderPlaneProjector::getRotation(const osg::Vec3d& p1, bool p1OnCyl, const osg::Vec3d& p2, bool p2OnCyl) const
{
if (p1OnCyl && p2OnCyl)
{
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)
if(_parallelPlane)
{
osg::Vec3d closestPointToPlaneLine1, closestPointToPlaneLine2;
computeClosestPointOnLine(_planeLineStart, _planeLineEnd,
@ -656,32 +613,28 @@ osg::Quat CylinderPlaneProjector::getRotation(const osg::Vec3d& p1, bool p1OnCyl
osg::Vec3d diff = v2 - v1;
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());
osg::Vec3d rotAxis = _plane.getNormal() ^ v1;
if (v2.length() > v1.length())
return osg::Quat(angle, rotAxis);
return osg::Quat(angle, rotAxis);
else
return osg::Quat(-angle, rotAxis);
return osg::Quat(-angle, rotAxis);
}
else
{
osg::Vec3d offCylinderPt = (p1OnCyl) ? p2 : p1;
osg::Vec3d v1 = p1 - getCylinder()->getCenter();
osg::Vec3d v2 = p2 - getCylinder()->getCenter();
osg::Vec3d linePtNearest;
computeClosestPointOnLine(_planeLineStart, _planeLineEnd,
offCylinderPt, linePtNearest);
osg::Vec3d dirToOffCylinderPt = offCylinderPt - linePtNearest;
dirToOffCylinderPt.normalize();
double cosAngle = v1 * v2 / (v1.length() * v2.length());
osg::Vec3d ptOnCylinder = linePtNearest + dirToOffCylinderPt * getCylinder()->getRadius();
if (cosAngle > 1.0 || cosAngle < -1.0)
return osg::Quat();
if (p1OnCyl)
return (getRotation(p1, true, ptOnCylinder, true) *
getRotation(ptOnCylinder, false, p2, false));
else
return (getRotation(p1, false, ptOnCylinder, false) *
getRotation(ptOnCylinder, true, p2, true));
double angle = acosf(cosAngle);
osg::Vec3d rotAxis = v1 ^ v2;
return osg::Quat(angle, rotAxis);
}
}