diff --git a/include/osgUtil/IntersectionVisitor b/include/osgUtil/IntersectionVisitor index b513e525d..9be362252 100644 --- a/include/osgUtil/IntersectionVisitor +++ b/include/osgUtil/IntersectionVisitor @@ -50,9 +50,9 @@ class Intersector : public osg::Referenced LIMIT_NEAREST }; - Intersector(CoordinateFrame cf=MODEL): + Intersector(CoordinateFrame cf=MODEL, IntersectionLimit il=NO_LIMIT): _coordinateFrame(cf), - _intersectionLimit(NO_LIMIT), + _intersectionLimit(il), _disabledCount(0) {} diff --git a/include/osgUtil/LineSegmentIntersector b/include/osgUtil/LineSegmentIntersector index 95fb79520..9872ec319 100644 --- a/include/osgUtil/LineSegmentIntersector +++ b/include/osgUtil/LineSegmentIntersector @@ -25,11 +25,12 @@ class OSGUTIL_EXPORT LineSegmentIntersector : public Intersector { public: - /** Construct a LineSegmentIntersector the runs between the specified start and end points in MODEL coordinates. */ + /** Construct a LineSegmentIntersector that runs between the specified start and end points in MODEL coordinates. */ LineSegmentIntersector(const osg::Vec3d& start, const osg::Vec3d& end); - /** Construct a LineSegmentIntersector the runs between the specified start and end points in the specified coordinate frame. */ - LineSegmentIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& end); + /** Construct a LineSegmentIntersector that runs between the specified start and end points in the specified coordinate frame. */ + LineSegmentIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& end, LineSegmentIntersector* parent = NULL, + osgUtil::Intersector::IntersectionLimit intersectionLimit = osgUtil::Intersector::NO_LIMIT); /** Convenience constructor for supporting picking in WINDOW, or PROJECTION coordinates * In WINDOW coordinates creates a start value of (x,y,0) and end value of (x,y,1). @@ -94,11 +95,19 @@ class OSGUTIL_EXPORT LineSegmentIntersector : public Intersector virtual void intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable); + virtual void intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable, + const osg::Vec3d& s, const osg::Vec3d& e); + virtual void reset(); virtual bool containsIntersections() { return !getIntersections().empty(); } - protected: + /** Compute the matrix that transforms the local coordinate system of parent Intersector (usually + the current intersector) into the child coordinate system of the child Intersector. + cf parameter indicates the coordinate frame of parent Intersector. */ + static osg::Matrix getTransformation(osgUtil::IntersectionVisitor& iv, CoordinateFrame cf); + +protected: bool intersects(const osg::BoundingSphere& bs); bool intersectAndClip(osg::Vec3d& s, osg::Vec3d& e,const osg::BoundingBox& bb); diff --git a/include/osgUtil/RayIntersector b/include/osgUtil/RayIntersector new file mode 100644 index 000000000..f07978d4c --- /dev/null +++ b/include/osgUtil/RayIntersector @@ -0,0 +1,128 @@ +/* -*-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 OSGUTIL_RAYINTERSECTOR +#define OSGUTIL_RAYINTERSECTOR 1 + +#include + +namespace osgUtil +{ + +/** RayIntersector implements possibly-infinite line intersections with the scene graph. + * + * Compared with LineSegmentIntersector, RayIntersector supports infinite intersection + * lines, start and end point can be given in homogeneous coordinates and projection + * matrix is allowed to have z-far plane at infinity (often used in shadow volume + * technique). + * + * Currently, picking of objects at infinity is not supported. Please, contribute. + * + * The class is be used in conjunction with IntersectionVisitor. */ +class OSGUTIL_EXPORT RayIntersector : public Intersector +{ + public: + + /** Construct a RayIntersector. You will need to provide start and end point, + * or start point and direction. See setStart() and setDirecton(). */ + RayIntersector(CoordinateFrame cf = MODEL, RayIntersector* parent = NULL, + osgUtil::Intersector::IntersectionLimit intersectionLimit = osgUtil::Intersector::NO_LIMIT); + + /** Construct a RayIntersector that runs from start point in specified direction to the infinity. + * Start and direction are provided in MODEL coordinates. */ + RayIntersector(const osg::Vec3d& start, const osg::Vec3d& direction); + + /** Construct a RayIntersector the runs from start point in specified direction to the infinity in the specified coordinate frame. */ + RayIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& direction, RayIntersector* parent = NULL, + osgUtil::Intersector::IntersectionLimit intersectionLimit = osgUtil::Intersector::NO_LIMIT); + + /** Convenience constructor for supporting picking in WINDOW and PROJECTION coordinates. + * In WINDOW coordinates, it creates a start value of (x,y,0) and end value of (x,y,1). + * In PROJECTION coordinates (clip space cube), it creates a start value of (x,y,-1) and end value of (x,y,1). + * In VIEW and MODEL coordinates, it creates a start value of (x,y,0) and end value of (x,y,1).*/ + RayIntersector(CoordinateFrame cf, double x, double y); + + struct OSGUTIL_EXPORT Intersection + { + Intersection() : distance(-1.0), primitiveIndex(0) {} + + bool operator < (const Intersection& rhs) const { return distance < rhs.distance; } + + typedef std::vector IndexList; + typedef std::vector RatioList; + + double distance; + osg::NodePath nodePath; + osg::ref_ptr drawable; + osg::ref_ptr matrix; + osg::Vec3d localIntersectionPoint; + osg::Vec3 localIntersectionNormal; + IndexList indexList; + RatioList ratioList; + unsigned int primitiveIndex; + + const osg::Vec3d& getLocalIntersectPoint() const { return localIntersectionPoint; } + osg::Vec3d getWorldIntersectPoint() const { return matrix.valid() ? localIntersectionPoint * (*matrix) : localIntersectionPoint; } + + const osg::Vec3& getLocalIntersectNormal() const { return localIntersectionNormal; } + osg::Vec3 getWorldIntersectNormal() const { return matrix.valid() ? osg::Matrix::transform3x3(osg::Matrix::inverse(*matrix),localIntersectionNormal) : localIntersectionNormal; } + + /** convinience function for mapping the intersection point to any textures assigned to the objects intersected. + * Returns the Texture pointer and texture coords of object hit when a texture is available on the object, returns NULL otherwise.*/ + osg::Texture* getTextureLookUp(osg::Vec3& tc) const; + + }; + + typedef std::multiset Intersections; + + inline void insertIntersection(const Intersection& intersection) { getIntersections().insert(intersection); } + inline Intersections& getIntersections() { return _parent ? _parent->_intersections : _intersections; } + inline Intersection getFirstIntersection() { Intersections& intersections = getIntersections(); return intersections.empty() ? Intersection() : *(intersections.begin()); } + + virtual void setStart(const osg::Vec3d& start) { _start = start; } + inline const osg::Vec3d& getStart() const { return _start; } + + virtual void setDirection(const osg::Vec3d& dir) { _direction = dir; } + inline const osg::Vec3d& getDirection() const { return _direction; } + + public: + + virtual Intersector* clone(osgUtil::IntersectionVisitor& iv); + + virtual bool enter(const osg::Node& node); + + virtual void leave(); + + virtual void intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable); + + virtual void reset(); + + virtual bool containsIntersections() { return !getIntersections().empty(); } + + protected: + + virtual bool intersects(const osg::BoundingSphere& bs); + bool intersectAndClip(osg::Vec3d& s, const osg::Vec3d& d, osg::Vec3d& e, const osg::BoundingBox& bb); + + RayIntersector* _parent; + + osg::Vec3d _start; + osg::Vec3d _direction; + + Intersections _intersections; + +}; + +} + +#endif diff --git a/src/osgUtil/CMakeLists.txt b/src/osgUtil/CMakeLists.txt index db6a39912..54f055a37 100644 --- a/src/osgUtil/CMakeLists.txt +++ b/src/osgUtil/CMakeLists.txt @@ -32,6 +32,7 @@ SET(TARGET_H ${HEADER_PATH}/PolytopeIntersector ${HEADER_PATH}/PositionalStateContainer ${HEADER_PATH}/PrintVisitor + ${HEADER_PATH}/RayIntersector ${HEADER_PATH}/ReflectionMapGenerator ${HEADER_PATH}/RenderBin ${HEADER_PATH}/RenderLeaf @@ -74,6 +75,7 @@ SET(TARGET_SRC PolytopeIntersector.cpp PositionalStateContainer.cpp PrintVisitor.cpp + RayIntersector.cpp RenderBin.cpp RenderLeaf.cpp RenderStage.cpp diff --git a/src/osgUtil/LineSegmentIntersector.cpp b/src/osgUtil/LineSegmentIntersector.cpp index 7ffa743b5..0310d32c6 100644 --- a/src/osgUtil/LineSegmentIntersector.cpp +++ b/src/osgUtil/LineSegmentIntersector.cpp @@ -77,7 +77,7 @@ namespace LineSegmentIntersectorUtils _limitOneIntersection = false; } - void set(const osg::Vec3d& start, osg::Vec3d& end, float ratio=FLT_MAX) + void set(const osg::Vec3d& start, const osg::Vec3d& end, float ratio=FLT_MAX) { _hit=false; _index = 0; @@ -216,8 +216,9 @@ LineSegmentIntersector::LineSegmentIntersector(const osg::Vec3d& start, const os { } -LineSegmentIntersector::LineSegmentIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& end): - Intersector(cf), +LineSegmentIntersector::LineSegmentIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& end, + LineSegmentIntersector* parent, osgUtil::Intersector::IntersectionLimit intersectionLimit): + Intersector(cf, intersectionLimit), _parent(0), _start(start), _end(end) @@ -249,8 +250,18 @@ Intersector* LineSegmentIntersector::clone(osgUtil::IntersectionVisitor& iv) // compute the matrix that takes this Intersector from its CoordinateFrame into the local MODEL coordinate frame // that geometry in the scene graph will always be in. + osg::Matrix matrix(getTransformation(iv, _coordinateFrame)); + + osg::ref_ptr lsi = new LineSegmentIntersector(_start * matrix, _end * matrix); + lsi->_parent = this; + lsi->_intersectionLimit = this->_intersectionLimit; + return lsi.release(); +} + +osg::Matrix LineSegmentIntersector::getTransformation(IntersectionVisitor& iv, CoordinateFrame cf) +{ osg::Matrix matrix; - switch (_coordinateFrame) + switch (cf) { case(WINDOW): if (iv.getWindowMatrix()) matrix.preMult( *iv.getWindowMatrix() ); @@ -274,11 +285,7 @@ Intersector* LineSegmentIntersector::clone(osgUtil::IntersectionVisitor& iv) osg::Matrix inverse; inverse.invert(matrix); - - osg::ref_ptr lsi = new LineSegmentIntersector(_start * inverse, _end * inverse); - lsi->_parent = this; - lsi->_intersectionLimit = this->_intersectionLimit; - return lsi.release(); + return inverse; } bool LineSegmentIntersector::enter(const osg::Node& node) @@ -301,6 +308,12 @@ void LineSegmentIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Dr if (iv.getDoDummyTraversal()) return; + intersect(iv, drawable, s, e); +} + +void LineSegmentIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable, + const osg::Vec3d& s, const osg::Vec3d& e) +{ osg::KdTree* kdTree = iv.getUseKdTreeWhenAvailable() ? dynamic_cast(drawable->getShape()) : 0; if (kdTree) { diff --git a/src/osgUtil/RayIntersector.cpp b/src/osgUtil/RayIntersector.cpp new file mode 100644 index 000000000..f829151bf --- /dev/null +++ b/src/osgUtil/RayIntersector.cpp @@ -0,0 +1,358 @@ +/* -*-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 +#include +#include +#include +#include +#include +#include + +using namespace osg; +using namespace osgUtil; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// RayIntersector +// + +RayIntersector::RayIntersector(CoordinateFrame cf, RayIntersector* parent, + Intersector::IntersectionLimit intersectionLimit) : + Intersector(cf, intersectionLimit), + _parent(parent) +{ +} + +RayIntersector::RayIntersector(const Vec3d& start, const Vec3d& direction) : + Intersector(), + _start(start), + _direction(direction) +{ +} + +RayIntersector::RayIntersector(CoordinateFrame cf, const Vec3d& start, const Vec3d& direction, + RayIntersector* parent, Intersector::IntersectionLimit intersectionLimit) : + Intersector(cf, intersectionLimit), + _parent(parent), + _start(start), + _direction(direction) +{ +} + +RayIntersector::RayIntersector(CoordinateFrame cf, double x, double y) : + Intersector(cf), + _parent(0) +{ + switch(cf) + { + case WINDOW: setStart(Vec3d(x,y,0.)); setDirection(Vec3d(0.,0.,1.)); break; + case PROJECTION: setStart(Vec3d(x,y,-1.)); setDirection(Vec3d(0.,0.,1.)); break; + case VIEW: setStart(Vec3d(x,y,0.)); setDirection(Vec3d(0.,0.,1.)); break; + case MODEL: setStart(Vec3d(x,y,0.)); setDirection(Vec3d(0.,0.,1.)); break; + } +} + +Intersector* RayIntersector::clone(IntersectionVisitor& iv) +{ + if (_coordinateFrame==MODEL && iv.getModelMatrix()==0) + { + return new RayIntersector(MODEL, _start, _direction, this, _intersectionLimit); + } + + Matrix matrix(LineSegmentIntersector::getTransformation(iv, _coordinateFrame)); + + Vec3d newStart = _start * matrix; + Vec4d tmp = Vec4d(_start + _direction, 1.) * matrix; + Vec3d newEnd = Vec3d(tmp.x(), tmp.y(), tmp.z()) - (newStart * tmp.w()); + return new RayIntersector(MODEL, newStart, newEnd, this, _intersectionLimit); +} + +bool RayIntersector::enter(const Node& node) +{ + if (reachedLimit()) return false; + return !node.isCullingActive() || intersects( node.getBound() ); +} + +void RayIntersector::leave() +{ + // do nothing +} + +void RayIntersector::reset() +{ + Intersector::reset(); + + _intersections.clear(); +} + +void RayIntersector::intersect(IntersectionVisitor& iv, Drawable* drawable) +{ + // did we reached what we wanted as specified by setIntersectionLimit()? + if (reachedLimit()) return; + + // clip ray to finite line segment + Vec3d s(_start), e; + if (!intersectAndClip(s, _direction, e, drawable->getBound())) return; + + // dummy traversal + if (iv.getDoDummyTraversal()) return; + + // get intersections using LineSegmentIntersector + LineSegmentIntersector lsi(MODEL, s, e, NULL, _intersectionLimit); + lsi.intersect(iv, drawable, s, e); + + // copy intersections from LineSegmentIntersector + LineSegmentIntersector::Intersections intersections = lsi.getIntersections(); + if (!intersections.empty()) + { + double preLength = (s - _start).length(); + double esLength = (e - s).length(); + + for(LineSegmentIntersector::Intersections::iterator it = intersections.begin(); + it != intersections.end(); it++) + { + Intersection hit; + hit.distance = preLength + it->ratio * esLength; + hit.matrix = it->matrix; + hit.nodePath = it->nodePath; + hit.drawable = it->drawable; + hit.primitiveIndex = it->primitiveIndex; + + hit.localIntersectionPoint = it->localIntersectionPoint; + hit.localIntersectionNormal = it->localIntersectionNormal; + + hit.indexList = it->indexList; + hit.ratioList = it->ratioList; + + insertIntersection(hit); + } + } +} + +bool RayIntersector::intersects(const BoundingSphere& bs) +{ + // if bs not valid then return true based on the assumption that an invalid sphere is yet to be defined. + if (!bs.valid()) return true; + + // test for _start inside the bounding sphere + Vec3d sm = _start - bs._center; + double c = sm.length2() - bs._radius * bs._radius; + if (c<0.0) return true; + + // solve quadratic equation + double a = _direction.length2(); + double b = (sm * _direction) * 2.0; + double d = b * b - 4.0 * a * c; + + // no intersections if d<0 + if (d<0.0) return false; + + // compute two solutions of quadratic equation + d = sqrt(d); + double div = 1.0/(2.0*a); + double r1 = (-b-d)*div; + double r2 = (-b+d)*div; + + // return false if both intersections are before the ray start + if (r1<=0.0 && r2<=0.0) return false; + + // if LIMIT_NEAREST and closest point of bounding sphere is further than already found intersection, return false + if (_intersectionLimit == LIMIT_NEAREST && !getIntersections().empty()) + { + double minDistance = sm.length() - bs._radius; + if (minDistance >= getIntersections().begin()->distance) return false; + } + + // passed all the rejection tests so line must intersect bounding sphere, return true. + return true; +} + +bool RayIntersector::intersectAndClip(Vec3d& s, const Vec3d& d, Vec3d& e, const BoundingBox& bbInput) +{ + // bounding box min and max + Vec3d bb_min(bbInput._min); + Vec3d bb_max(bbInput._max); + + // Expand the extents of the bounding box by the epsilon to prevent numerical errors resulting in misses. + const double epsilon = 1e-6; + + // clip s against all three components of the Min to Max range of bb + for (int i=0; i<3; i++) + { + // test direction + if (d[i] >= 0.) + { + // trivial reject of segment wholly outside + if (s[i] > bb_max[i]) return false; + + if (s[i] < bb_min[i]) + { + // clip s to xMin + double t = (bb_min[i]-s[i])/d[i] - epsilon; + if (t>0.0) s = s + d*t; + } + } + else + { + // trivial reject of segment wholly outside + if (s[i] < bb_min[i]) return false; + + if (s[i] > bb_max[i]) + { + // clip s to xMax + double t = (bb_max[i]-s[i])/d[i] - epsilon; + if (t>0.0) s = s + d*t; + } + } + } + + // t for ending point of clipped ray + double end_t = std::numeric_limits::infinity(); + + // get end point by clipping the ray by bb + // note: this can not be done in previous loop as start point s is moving + for (int i=0; i<3; i++) + { + // test direction + if (d[i] >= 0.) + { + // compute end_t based on xMax + double t = (bb_max[i]-s[i])/d[i] + epsilon; + if (t < end_t) + end_t = t; + } + else + { + // compute end_t based on xMin + double t = (bb_min[i]-s[i])/d[i] + epsilon; + if (t < end_t) + end_t = t; + } + } + + // compute e + e = s + d*end_t; + + return true; +} + +Texture* RayIntersector::Intersection::getTextureLookUp(Vec3& tc) const +{ + Geometry* geometry = drawable.valid() ? drawable->asGeometry() : 0; + Vec3Array* vertices = geometry ? dynamic_cast(geometry->getVertexArray()) : 0; + + if (vertices) + { + if (indexList.size()==3 && ratioList.size()==3) + { + unsigned int i1 = indexList[0]; + unsigned int i2 = indexList[1]; + unsigned int i3 = indexList[2]; + + float r1 = ratioList[0]; + float r2 = ratioList[1]; + float r3 = ratioList[2]; + + Array* texcoords = (geometry->getNumTexCoordArrays()>0) ? geometry->getTexCoordArray(0) : 0; + FloatArray* texcoords_FloatArray = dynamic_cast(texcoords); + Vec2Array* texcoords_Vec2Array = dynamic_cast(texcoords); + Vec3Array* texcoords_Vec3Array = dynamic_cast(texcoords); + if (texcoords_FloatArray) + { + // we have tex coord array so now we can compute the final tex coord at the point of intersection. + float tc1 = (*texcoords_FloatArray)[i1]; + float tc2 = (*texcoords_FloatArray)[i2]; + float tc3 = (*texcoords_FloatArray)[i3]; + tc.x() = tc1*r1 + tc2*r2 + tc3*r3; + } + else if (texcoords_Vec2Array) + { + // we have tex coord array so now we can compute the final tex coord at the point of intersection. + const Vec2& tc1 = (*texcoords_Vec2Array)[i1]; + const Vec2& tc2 = (*texcoords_Vec2Array)[i2]; + const Vec2& tc3 = (*texcoords_Vec2Array)[i3]; + tc.x() = tc1.x()*r1 + tc2.x()*r2 + tc3.x()*r3; + tc.y() = tc1.y()*r1 + tc2.y()*r2 + tc3.y()*r3; + } + else if (texcoords_Vec3Array) + { + // we have tex coord array so now we can compute the final tex coord at the point of intersection. + const Vec3& tc1 = (*texcoords_Vec3Array)[i1]; + const Vec3& tc2 = (*texcoords_Vec3Array)[i2]; + const Vec3& tc3 = (*texcoords_Vec3Array)[i3]; + tc.x() = tc1.x()*r1 + tc2.x()*r2 + tc3.x()*r3; + tc.y() = tc1.y()*r1 + tc2.y()*r2 + tc3.y()*r3; + tc.z() = tc1.z()*r1 + tc2.z()*r2 + tc3.z()*r3; + } + else + { + return 0; + } + } + + const TexMat* activeTexMat = 0; + const Texture* activeTexture = 0; + + if (drawable->getStateSet()) + { + const TexMat* texMat = dynamic_cast(drawable->getStateSet()->getTextureAttribute(0,StateAttribute::TEXMAT)); + if (texMat) activeTexMat = texMat; + + const Texture* texture = dynamic_cast(drawable->getStateSet()->getTextureAttribute(0,StateAttribute::TEXTURE)); + if (texture) activeTexture = texture; + } + + for(NodePath::const_reverse_iterator itr = nodePath.rbegin(); + itr != nodePath.rend() && (!activeTexMat || !activeTexture); + ++itr) + { + const Node* node = *itr; + if (node->getStateSet()) + { + if (!activeTexMat) + { + const TexMat* texMat = dynamic_cast(node->getStateSet()->getTextureAttribute(0,StateAttribute::TEXMAT)); + if (texMat) activeTexMat = texMat; + } + + if (!activeTexture) + { + const Texture* texture = dynamic_cast(node->getStateSet()->getTextureAttribute(0,StateAttribute::TEXTURE)); + if (texture) activeTexture = texture; + } + } + } + + if (activeTexMat) + { + Vec4 tc_transformed = Vec4(tc.x(), tc.y(), tc.z() ,0.0f) * activeTexMat->getMatrix(); + tc.x() = tc_transformed.x(); + tc.y() = tc_transformed.y(); + tc.z() = tc_transformed.z(); + + if (activeTexture && activeTexMat->getScaleByTextureRectangleSize()) + { + tc.x() *= static_cast(activeTexture->getTextureWidth()); + tc.y() *= static_cast(activeTexture->getTextureHeight()); + tc.z() *= static_cast(activeTexture->getTextureDepth()); + } + } + + return const_cast(activeTexture); + + } + return 0; +}