363 lines
12 KiB
C++
363 lines
12 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 <osgUtil/RayIntersector>
|
|
#include <osgUtil/LineSegmentIntersector>
|
|
#include <osg/KdTree>
|
|
#include <osg/Notify>
|
|
#include <osg/TexMat>
|
|
#include <limits>
|
|
#include <cmath>
|
|
|
|
using namespace osg;
|
|
using namespace osgUtil;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// RayIntersector
|
|
//
|
|
|
|
RayIntersector::RayIntersector(CoordinateFrame cf, RayIntersector* parent,
|
|
Intersector::IntersectionLimit intersectionLimit) :
|
|
Intersector(cf, intersectionLimit),
|
|
_parent(parent)
|
|
{
|
|
if (parent) setPrecisionHint(parent->getPrecisionHint());
|
|
}
|
|
|
|
RayIntersector::RayIntersector(const Vec3d& start, const Vec3d& direction) :
|
|
Intersector(),
|
|
_parent(0),
|
|
_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)
|
|
{
|
|
if (parent) setPrecisionHint(parent->getPrecisionHint());
|
|
}
|
|
|
|
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->getBoundingBox())) return;
|
|
|
|
// dummy traversal
|
|
if (iv.getDoDummyTraversal()) return;
|
|
|
|
// get intersections using LineSegmentIntersector
|
|
LineSegmentIntersector lsi(MODEL, s, e, NULL, _intersectionLimit);
|
|
lsi.setPrecisionHint(getPrecisionHint());
|
|
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<double>::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<Vec3Array*>(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<FloatArray*>(texcoords);
|
|
Vec2Array* texcoords_Vec2Array = dynamic_cast<Vec2Array*>(texcoords);
|
|
Vec3Array* texcoords_Vec3Array = dynamic_cast<Vec3Array*>(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<TexMat*>(drawable->getStateSet()->getTextureAttribute(0,StateAttribute::TEXMAT));
|
|
if (texMat) activeTexMat = texMat;
|
|
|
|
const Texture* texture = dynamic_cast<Texture*>(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<const TexMat*>(node->getStateSet()->getTextureAttribute(0,StateAttribute::TEXMAT));
|
|
if (texMat) activeTexMat = texMat;
|
|
}
|
|
|
|
if (!activeTexture)
|
|
{
|
|
const Texture* texture = dynamic_cast<const Texture*>(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<float>(activeTexture->getTextureWidth());
|
|
tc.y() *= static_cast<float>(activeTexture->getTextureHeight());
|
|
tc.z() *= static_cast<float>(activeTexture->getTextureDepth());
|
|
}
|
|
}
|
|
|
|
return const_cast<Texture*>(activeTexture);
|
|
|
|
}
|
|
return 0;
|
|
}
|