/************************************************************** ggt-head beg * * GGT: Generic Graphics Toolkit * * Original Authors: * Allen Bierbaum * * ----------------------------------------------------------------- * File: Intersection.h,v * Date modified: 2006/06/08 21:11:59 * Version: 1.25 * ----------------------------------------------------------------- * *********************************************************** ggt-head end */ /*************************************************************** ggt-cpr beg * * GGT: The Generic Graphics Toolkit * Copyright (C) 2001,2002 Allen Bierbaum * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * 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 GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ************************************************************ ggt-cpr end */ #ifndef _GMTL_INTERSECTION_H_ #define _GMTL_INTERSECTION_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace gmtl { /** * Tests if the given AABoxes intersect with each other. Sharing an edge IS * considered intersection by this algorithm. * * @param box1 the first AA box to test * @param box2 the second AA box to test * * @return true if the boxes intersect; false otherwise */ template bool intersect(const AABox& box1, const AABox& box2) { // Look for a separating axis on each box for each axis if (box1.getMin()[0] > box2.getMax()[0]) return false; if (box1.getMin()[1] > box2.getMax()[1]) return false; if (box1.getMin()[2] > box2.getMax()[2]) return false; if (box2.getMin()[0] > box1.getMax()[0]) return false; if (box2.getMin()[1] > box1.getMax()[1]) return false; if (box2.getMin()[2] > box1.getMax()[2]) return false; // No separating axis ... they must intersect return true; } /** * Tests if the given AABox and point intersect with each other. On an edge IS * considered intersection by this algorithm. * * @param box the box to test * @param point the point to test * * @return true if the point is within the box's bounds; false otherwise */ template bool intersect( const AABox& box, const Point& point ) { // Look for a separating axis on each box for each axis if (box.getMin()[0] > point[0]) return false; if (box.getMin()[1] > point[1]) return false; if (box.getMin()[2] > point[2]) return false; if (point[0] > box.getMax()[0]) return false; if (point[1] > box.getMax()[1]) return false; if (point[2] > box.getMax()[2]) return false; // they must intersect return true; } /** * Tests if the given AABoxes intersect if moved along the given paths. Using * the AABox sweep test, the normalized time of the first and last points of * contact are found. * * @param box1 the first box to test * @param path1 the path the first box should travel along * @param box2 the second box to test * @param path2 the path the second box should travel along * @param firstContact set to the normalized time of the first point of contact * @param secondContact set to the normalized time of the second point of contact * * @return true if the boxes intersect at any time; false otherwise */ template bool intersect( const AABox& box1, const Vec& path1, const AABox& box2, const Vec& path2, DATA_TYPE& firstContact, DATA_TYPE& secondContact ) { // Algorithm taken from Gamasutra's article, "Simple Intersection Test for // Games" - http://www.gamasutra.com/features/19991018/Gomez_3.htm // // This algorithm is solved from the frame of reference of box1 // Get the relative path (in normalized time) Vec path = path2 - path1; // The first time of overlap along each axis Vec overlap1(DATA_TYPE(0), DATA_TYPE(0), DATA_TYPE(0)); // The second time of overlap along each axis Vec overlap2(DATA_TYPE(1), DATA_TYPE(1), DATA_TYPE(1)); // Check if the boxes already overlap if (gmtl::intersect(box1, box2)) { firstContact = secondContact = DATA_TYPE(0); return true; } // Find the possible first and last times of overlap along each axis for (int i=0; i<3; ++i) { if ((box1.getMax()[i] < box2.getMin()[i]) && (path[i] < DATA_TYPE(0))) { overlap1[i] = (box1.getMax()[i] - box2.getMin()[i]) / path[i]; } else if ((box2.getMax()[i] < box1.getMin()[i]) && (path[i] > DATA_TYPE(0))) { overlap1[i] = (box1.getMin()[i] - box2.getMax()[i]) / path[i]; } if ((box2.getMax()[i] > box1.getMin()[i]) && (path[i] < DATA_TYPE(0))) { overlap2[i] = (box1.getMin()[i] - box2.getMax()[i]) / path[i]; } else if ((box1.getMax()[i] > box2.getMin()[i]) && (path[i] > DATA_TYPE(0))) { overlap2[i] = (box1.getMax()[i] - box2.getMin()[i]) / path[i]; } } // Calculate the first time of overlap firstContact = Math::Max(overlap1[0], overlap1[1], overlap1[2]); // Calculate the second time of overlap secondContact = Math::Min(overlap2[0], overlap2[1], overlap2[2]); // There could only have been a collision if the first overlap time // occurred before the second overlap time return firstContact <= secondContact; } /** * Given an axis-aligned bounding box and a ray (or subclass thereof), * returns whether the ray intersects the box, and if so, \p tIn and * \p tOut are set to the parametric terms on the ray where the segment * enters and exits the box respectively. * * The implementation of this function comes from the book * Geometric Tools for Computer Graphics, pages 626-630. * * @note Internal function for performing an intersection test between an * axis-aligned bounding box and a ray. User code should not call this * function directly. It is used to capture the common code between * the gmtl::Ray and gmtl::LineSeg overloads of * gmtl::intersect() when intersecting with a gmtl::AABox. */ template bool intersectAABoxRay(const AABox& box, const Ray& ray, DATA_TYPE& tIn, DATA_TYPE& tOut) { tIn = -std::numeric_limits::max(); tOut = std::numeric_limits::max(); DATA_TYPE t0, t1; const DATA_TYPE epsilon(0.0000001); // YZ plane. if ( gmtl::Math::abs(ray.mDir[0]) < epsilon ) { // Ray parallel to plane. if ( ray.mOrigin[0] < box.mMin[0] || ray.mOrigin[0] > box.mMax[0] ) { return false; } } // XZ plane. if ( gmtl::Math::abs(ray.mDir[1]) < epsilon ) { // Ray parallel to plane. if ( ray.mOrigin[1] < box.mMin[1] || ray.mOrigin[1] > box.mMax[1] ) { return false; } } // XY plane. if ( gmtl::Math::abs(ray.mDir[2]) < epsilon ) { // Ray parallel to plane. if ( ray.mOrigin[2] < box.mMin[2] || ray.mOrigin[2] > box.mMax[2] ) { return false; } } // YZ plane. t0 = (box.mMin[0] - ray.mOrigin[0]) / ray.mDir[0]; t1 = (box.mMax[0] - ray.mOrigin[0]) / ray.mDir[0]; if ( t0 > t1 ) { std::swap(t0, t1); } if ( t0 > tIn ) { tIn = t0; } if ( t1 < tOut ) { tOut = t1; } if ( tIn > tOut || tOut < DATA_TYPE(0) ) { return false; } // XZ plane. t0 = (box.mMin[1] - ray.mOrigin[1]) / ray.mDir[1]; t1 = (box.mMax[1] - ray.mOrigin[1]) / ray.mDir[1]; if ( t0 > t1 ) { std::swap(t0, t1); } if ( t0 > tIn ) { tIn = t0; } if ( t1 < tOut ) { tOut = t1; } if ( tIn > tOut || tOut < DATA_TYPE(0) ) { return false; } // XY plane. t0 = (box.mMin[2] - ray.mOrigin[2]) / ray.mDir[2]; t1 = (box.mMax[2] - ray.mOrigin[2]) / ray.mDir[2]; if ( t0 > t1 ) { std::swap(t0, t1); } if ( t0 > tIn ) { tIn = t0; } if ( t1 < tOut ) { tOut = t1; } if ( tIn > tOut || tOut < DATA_TYPE(0) ) { return false; } return true; } /** * Given a line segment and an axis-aligned bounding box, returns whether * the line intersects the box, and if so, \p tIn and \p tOut are set to * the parametric terms on the line segment where the segment enters and * exits the box respectively. * * @since 0.4.11 */ template bool intersect(const AABox& box, const LineSeg& seg, unsigned int& numHits, DATA_TYPE& tIn, DATA_TYPE& tOut) { numHits = 0; bool result = intersectAABoxRay(box, seg, tIn, tOut); if ( result ) { // If tIn is less than 0, then the origin of the line segment is // inside the bounding box (not on an edge)but the endpoint is // outside. if ( tIn < DATA_TYPE(0) ) { numHits = 1; tIn = tOut; } // If tIn is less than 0, then the origin of the line segment is // outside the bounding box but the endpoint is inside (not on an // edge). else if ( tOut > DATA_TYPE(1) ) { numHits = 1; tOut = tIn; } // Otherwise, the line segement intersects the bounding box in two // places. tIn and tOut reflect those points of intersection. else { numHits = 2; } } return result; } /** * Given a line segment and an axis-aligned bounding box, returns whether * the line intersects the box, and if so, \p tIn and \p tOut are set to * the parametric terms on the line segment where the segment enters and * exits the box respectively. * * @since 0.4.11 */ template bool intersect(const LineSeg& seg, const AABox& box, unsigned int& numHits, DATA_TYPE& tIn, DATA_TYPE& tOut) { return intersect(box, seg, numHits, tIn, tOut); } /** * Given a ray and an axis-aligned bounding box, returns whether the ray * intersects the box, and if so, \p tIn and \p tOut are set to the * parametric terms on the ray where it enters and exits the box * respectively. * * @since 0.4.11 */ template bool intersect(const AABox& box, const Ray& ray, unsigned int& numHits, DATA_TYPE& tIn, DATA_TYPE& tOut) { numHits = 0; bool result = intersectAABoxRay(box, ray, tIn, tOut); if ( result ) { // Ray is inside the box. if ( tIn < DATA_TYPE(0) ) { tIn = tOut; numHits = 1; } else { numHits = 2; } } return result; } /** * Given a ray and an axis-aligned bounding box, returns whether the ray * intersects the box, and if so, \p tIn and \p tOut are set to the * parametric terms on the ray where it enters and exits the box * respectively. * * @since 0.4.11 */ template bool intersect(const Ray& ray, const AABox& box, unsigned int& numHits, DATA_TYPE& tIn, DATA_TYPE& tOut) { return intersect(box, ray, numHits, tIn, tOut); } /** * Tests if the given Spheres intersect if moved along the given paths. Using * the Sphere sweep test, the normalized time of the first and last points of * contact are found. * * @param sph1 the first sphere to test * @param path1 the path the first sphere should travel along * @param sph2 the second sphere to test * @param path2 the path the second sphere should travel along * @param firstContact set to the normalized time of the first point of contact * @param secondContact set to the normalized time of the second point of contact * * @return true if the spheres intersect; false otherwise */ template bool intersect(const Sphere& sph1, const Vec& path1, const Sphere& sph2, const Vec& path2, DATA_TYPE& firstContact, DATA_TYPE& secondContact) { // Algorithm taken from Gamasutra's article, "Simple Intersection Test for // Games" - http://www.gamasutra.com/features/19991018/Gomez_2.htm // // This algorithm is solved from the frame of reference of sph1 // Get the relative path (in normalized time) const Vec path = path2 - path1; // Get the vector from sph1's starting point to sph2's starting point const Vec start_offset = sph2.getCenter() - sph1.getCenter(); // Compute the sum of the radii const DATA_TYPE radius_sum = sph1.getRadius() + sph2.getRadius(); // u*u coefficient const DATA_TYPE a = dot(path, path); // u coefficient const DATA_TYPE b = DATA_TYPE(2) * dot(path, start_offset); // constant term const DATA_TYPE c = dot(start_offset, start_offset) - radius_sum * radius_sum; // Check if they're already overlapping if (dot(start_offset, start_offset) <= radius_sum * radius_sum) { firstContact = secondContact = DATA_TYPE(0); return true; } // Find the first and last points of intersection if (Math::quadraticFormula(firstContact, secondContact, a, b, c)) { // Swap first and second contacts if necessary if (firstContact > secondContact) { std::swap(firstContact, secondContact); return true; } } return false; } /** * Tests if the given AABox and Sphere intersect with each other. On an edge * IS considered intersection by this algorithm. * * @param box the box to test * @param sph the sphere to test * * @return true if the items intersect; false otherwise */ template bool intersect(const AABox& box, const Sphere& sph) { DATA_TYPE dist_sqr = DATA_TYPE(0); // Compute the square of the distance from the sphere to the box for (int i=0; i<3; ++i) { if (sph.getCenter()[i] < box.getMin()[i]) { DATA_TYPE s = sph.getCenter()[i] - box.getMin()[i]; dist_sqr += s*s; } else if (sph.getCenter()[i] > box.getMax()[i]) { DATA_TYPE s = sph.getCenter()[i] - box.getMax()[i]; dist_sqr += s*s; } } return dist_sqr <= (sph.getRadius()*sph.getRadius()); } /** * Tests if the given AABox and Sphere intersect with each other. On an edge * IS considered intersection by this algorithm. * * @param sph the sphere to test * @param box the box to test * * @return true if the items intersect; false otherwise */ template bool intersect(const Sphere& sph, const AABox& box) { return gmtl::intersect(box, sph); } /** * intersect point/sphere. * @param point the point to test * @param sphere the sphere to test * @return true if point is in or on sphere */ template bool intersect( const Sphere& sphere, const Point& point ) { gmtl::Vec offset = point - sphere.getCenter(); DATA_TYPE dist = lengthSquared( offset ) - sphere.getRadius() * sphere.getRadius(); // point is inside the sphere when true return dist <= 0; } /** * intersect ray/sphere-shell (not volume). * only register hits with the surface of the sphere. * note: after calling this, you can find the intersection point with: ray.getOrigin() + ray.getDir() * t * * @param ray the ray to test * @param sphere the sphere to test * @return returns intersection point in t, and the number of hits * @return numhits, t0, t1 are undefined if return value is false */ template inline bool intersect( const Sphere& sphere, const Ray& ray, int& numhits, float& t0, float& t1 ) { numhits = -1; // set up quadratic Q(t) = a*t^2 + 2*b*t + c const Vec offset = ray.getOrigin() - sphere.getCenter(); const T a = lengthSquared( ray.getDir() ); const T b = dot( offset, ray.getDir() ); const T c = lengthSquared( offset ) - sphere.getRadius() * sphere.getRadius(); // no intersection if Q(t) has no real roots const T discriminant = b * b - a * c; if (discriminant < 0.0f) { numhits = 0; return false; } else if (discriminant > 0.0f) { T root = Math::sqrt( discriminant ); T invA = T(1) / a; t0 = (-b - root) * invA; t1 = (-b + root) * invA; // assert: t0 < t1 since A > 0 if (t0 >= T(0)) { numhits = 2; return true; } else if (t1 >= T(0)) { numhits = 1; t0 = t1; return true; } else { numhits = 0; return false; } } else { t0 = -b / a; if (t0 >= T(0)) { numhits = 1; return true; } else { numhits = 0; return false; } } } /** intersect LineSeg/Sphere-shell (not volume). * does intersection on sphere surface, point inside sphere doesn't count as an intersection * returns intersection point(s) in t * find intersection point(s) with: ray.getOrigin() + ray.getDir() * t * numhits, t0, t1 are undefined if return value is false */ template inline bool intersect( const Sphere& sphere, const LineSeg& lineseg, int& numhits, float& t0, float& t1 ) { if (intersect( sphere, Ray( lineseg ), numhits, t0, t1 )) { // throw out hits that are past 1 in segspace (off the end of the lineseg) while (0 < numhits && 1.0f < t0) { --numhits; t0 = t1; } if (2 == numhits && 1.0f < t1) { --numhits; } return 0 < numhits; } else { return false; } } /** * intersect lineseg/sphere-volume. * register hits with both the surface and when end points land on the interior of the sphere. * note: after calling this, you can find the intersection point with: ray.getOrigin() + ray.getDir() * t * * @param ray the lineseg to test * @param sphere the sphere to test * @return returns intersection point in t, and the number of hits * @return numhits, t0, t1 are undefined if return value is false */ template inline bool intersectVolume( const Sphere& sphere, const LineSeg& ray, int& numhits, float& t0, float& t1 ) { bool result = intersect( sphere, ray, numhits, t0, t1 ); if (result && numhits == 2) { return true; } // todo: make this faster (find an early out) since 1 or 0 hits is the common case. // volume test has some additional checks before we can throw it out because // one of both points may be inside the volume, so we want to return hits for those as well... else // 1 or 0 hits. { const T rsq = sphere.getRadius() * sphere.getRadius(); const Vec dist = ray.getOrigin() - sphere.getCenter(); const T a = lengthSquared( dist ) - rsq; const T b = lengthSquared( gmtl::Vec(dist + ray.getDir()) ) - rsq; bool inside1 = a <= T( 0 ); bool inside2 = b <= T( 0 ); // one point is inside if (numhits == 1 && inside1 && !inside2) { t1 = t0; t0 = T(0); numhits = 2; return true; } else if (numhits == 1 && !inside1 && inside2) { t1 = T(1); numhits = 2; return true; } // maybe both points are inside? else if (inside1 && inside2) // 0 hits. { t0 = T(0); t1 = T(1); numhits = 2; return true; } } return result; } /** * intersect ray/sphere-volume. * register hits with both the surface and when the origin lands in the interior of the sphere. * note: after calling this, you can find the intersection point with: ray.getOrigin() + ray.getDir() * t * * @param ray the ray to test * @param sphere the sphere to test * @return returns intersection point in t, and the number of hits * @return numhits, t0, t1 are undefined if return value is false */ template inline bool intersectVolume( const Sphere& sphere, const Ray& ray, int& numhits, float& t0, float& t1 ) { bool result = intersect( sphere, ray, numhits, t0, t1 ); if (result && numhits == 2) { return true; } else { const T rsq = sphere.getRadius() * sphere.getRadius(); const Vec dist = ray.getOrigin() - sphere.getCenter(); const T a = lengthSquared( dist ) - rsq; bool inside = a <= T( 0 ); // start point is inside if (inside) { t1 = t0; t0 = T(0); numhits = 2; return true; } } return result; } /** * Tests if the given plane and ray intersect with each other. * * @param ray - the Ray * @param plane - the Plane * @param t - t gives you the intersection point: * isect_point = ray.origin + ray.dir * t * * @return true if the ray intersects the plane. * @note If ray is parallel to plane: t=0, ret:true -> on plane, ret:false -> No hit */ template bool intersect( const Plane& plane, const Ray& ray, DATA_TYPE& t ) { const float eps(0.00001f); // t = -(n·P + d) Vec N( plane.getNormal() ); float denom( dot(N,ray.getDir()) ); if(gmtl::Math::abs(denom) < eps) // Ray parallel to plane { t = 0; if(distance(plane, ray.mOrigin) < eps) // Test for ray on plane { return true; } else { return false; } } t = dot( N, Vec(N * plane.getOffset() - ray.getOrigin()) ) / denom; return (DATA_TYPE)0 <= t; } /** * Tests if the given plane and lineseg intersect with each other. * * @param ray - the lineseg * @param plane - the Plane * @param t - t gives you the intersection point: * isect_point = lineseg.origin + lineseg.dir * t * * @return true if the lineseg intersects the plane. */ template bool intersect( const Plane& plane, const LineSeg& seg, DATA_TYPE& t ) { bool res(intersect(plane, static_cast >(seg), t)); return res && t <= (DATA_TYPE)1.0; } /** * Tests if the given triangle and ray intersect with each other. * * @param tri - the triangle (ccw ordering) * @param ray - the ray * @param u,v - tangent space u/v coordinates of the intersection * @param t - an indicator of the intersection location * @post t gives you the intersection point: * isect = ray.dir * t + ray.origin * @return true if the ray intersects the triangle. * @see from http://www.acm.org/jgt/papers/MollerTrumbore97/code.html */ template bool intersect( const Tri& tri, const Ray& ray, float& u, float& v, float& t ) { const float EPSILON = (DATA_TYPE)0.00001f; Vec edge1, edge2, tvec, pvec, qvec; float det,inv_det; /* find vectors for two edges sharing vert0 */ edge1 = tri[1] - tri[0]; edge2 = tri[2] - tri[0]; /* begin calculating determinant - also used to calculate U parameter */ gmtl::cross( pvec, ray.getDir(), edge2 ); /* if determinant is near zero, ray lies in plane of triangle */ det = gmtl::dot( edge1, pvec ); if (det < EPSILON) return false; /* calculate distance from vert0 to ray origin */ tvec = ray.getOrigin() - tri[0]; /* calculate U parameter and test bounds */ u = gmtl::dot( tvec, pvec ); if (u < 0.0 || u > det) return false; /* prepare to test V parameter */ gmtl::cross( qvec, tvec, edge1 ); /* calculate V parameter and test bounds */ v = gmtl::dot( ray.getDir(), qvec ); if (v < 0.0 || u + v > det) return false; /* calculate t, scale parameters, ray intersects triangle */ t = gmtl::dot( edge2, qvec ); inv_det = ((DATA_TYPE)1.0) / det; t *= inv_det; u *= inv_det; v *= inv_det; // test if t is within the ray boundary (when t >= 0) return t >= (DATA_TYPE)0; } /** * Tests if the given triangle and line segment intersect with each other. * * @param tri - the triangle (ccw ordering) * @param lineseg - the line segment * @param u,v - tangent space u/v coordinates of the intersection * @param t - an indicator of the intersection point * @post t gives you the intersection point: * isect = lineseg.getDir() * t + lineseg.getOrigin() * * @return true if the line segment intersects the triangle. */ template bool intersect( const Tri& tri, const LineSeg& lineseg, float& u, float& v, float& t ) { const DATA_TYPE eps = (DATA_TYPE)0.0001010101; DATA_TYPE l = length( lineseg.getDir() ); if (eps < l) { Ray temp( lineseg.getOrigin(), lineseg.getDir() ); bool result = intersect( tri, temp, u, v, t ); return result && t <= (DATA_TYPE)1.0; } else { return false; } } } #endif