diff options
Diffstat (limited to 'dep/src/g3dlite/CollisionDetection.cpp')
-rw-r--r-- | dep/src/g3dlite/CollisionDetection.cpp | 2455 |
1 files changed, 0 insertions, 2455 deletions
diff --git a/dep/src/g3dlite/CollisionDetection.cpp b/dep/src/g3dlite/CollisionDetection.cpp deleted file mode 100644 index 77eef0a5500..00000000000 --- a/dep/src/g3dlite/CollisionDetection.cpp +++ /dev/null @@ -1,2455 +0,0 @@ -/** - @file CollisionDetection.cpp - - @maintainer Morgan McGuire, http://graphics.cs.williams.edu - - @cite Bounce direction based on Paul Nettle's ftp://ftp.3dmaileffects.com/pub/FluidStudios/CollisionDetection/Fluid_Studios_Generic_Collision_Detection_for_Games_Using_Ellipsoids.pdf and comments by Max McGuire. Ray-sphere code by Eric Haines. - - @created 2001-11-24 - @edited 2008-12-29 - */ - -#include "G3D/CoordinateFrame.h" -#include "G3D/platform.h" -#include "G3D/CollisionDetection.h" -#include "G3D/debugAssert.h" -#include "G3D/vectorMath.h" -#include "G3D/Capsule.h" -#include "G3D/Plane.h" -#include "G3D/Line.h" -#include "G3D/LineSegment.h" -#include "G3D/Sphere.h" -#include "G3D/Box.h" -#include "G3D/Triangle.h" -#include "G3D/Vector3.h" -#include "G3D/AABox.h" - -#ifdef _MSC_VER -// Turn on fast floating-point optimizations -#pragma float_control( push ) -#pragma fp_contract( on ) -#pragma fenv_access( off ) -#pragma float_control( except, off ) -#pragma float_control( precise, off ) -#endif - - -namespace G3D { - -bool CollisionDetection::ignoreBool; -Vector3 CollisionDetection::ignore; -Array<Vector3> CollisionDetection::ignoreArray; - - - -Vector3 CollisionDetection::separatingAxisForSolidBoxSolidBox( - const int separatingAxisIndex, - const Box & box1, - const Box & box2) { - debugAssert(separatingAxisIndex >= 0); - debugAssert(separatingAxisIndex < 15); - Vector3 axis; - if (separatingAxisIndex < 3) { - axis = box1.axis(separatingAxisIndex); - } else if (separatingAxisIndex < 6) { - axis = box2.axis(separatingAxisIndex - 3); - } else { - int box1Index = (separatingAxisIndex - 6) / 3; - int box2Index = (separatingAxisIndex - 6) % 3; - axis = cross(box1.axis(box1Index), box2.axis(box2Index)); - } - return axis; -} - -#ifdef _MSC_VER -# pragma warning (push) -# pragma warning (disable : 4244) -#endif - -float CollisionDetection::projectedDistanceForSolidBoxSolidBox( - const int separatingAxisIndex, - const Vector3 & a, - const Vector3 & b, - const Vector3 & D, - const double* c, - const double* ca, - const double* ad, - const double* bd) -{ - (void)D; - - float R0 = 0.0f; - float R1 = 0.0f; - float R = 0.0f; - switch (separatingAxisIndex) { - case 0: - // A0 - R0 = a[0]; - R1 = b[0] * ca[0] + b[1] * ca[1] + b[2] * ca[2]; - R = fabs(ad[0]); - break; - case 1: - // A1 - R0 = a[1]; - R1 = b[0] * ca[3] + b[1] * ca[4] + b[2] * ca[5]; - R = fabs(ad[1]); - break; - case 2: - // A2 - R0 = a[2]; - R1 = b[0] * ca[6] + b[1] * ca[7] + b[2] * ca[8]; - R = fabs(ad[2]); - break; - case 3: - // B0 - R0 = a[0] * ca[0] + a[1] * ca[3] + a[2] * ca[6]; - R1 = b[0]; - R = fabs(bd[0]); - break; - case 4: - // B1 - R0 = a[0] * ca[1] + a[1] * ca[4] + a[2] * ca[7]; - R1 = b[1]; - R = fabs(bd[1]); - break; - case 5: - // B2 - R0 = a[0] * ca[2] + a[1] * ca[5] + a[2] * ca[8]; - R1 = b[2]; - R = fabs(bd[2]); - break; - case 6: - // A0 x B0 - R0 = a[1] * ca[6] + a[2] * ca[3]; - R1 = b[1] * ca[2] + b[2] * ca[1]; - R = fabs(c[3] * ad[2] - c[6] * ad[1]); - break; - case 7: - // A0 x B1 - R0 = a[1] * ca[7] + a[2] * ca[4]; - R1 = b[0] * ca[2] + b[2] * ca[0]; - R = fabs(c[4] * ad[2] - c[7] * ad[1]); - break; - case 8: - // A0 x B2 - R0 = a[1] * ca[8] + a[2] * ca[5]; - R1 = b[0] * ca[1] + b[1] * ca[0]; - R = fabs(c[5] * ad[2] - c[8] * ad[1]); - break; - case 9: - // A1 x B0 - R0 = a[0] * ca[6] + a[2] * ca[0]; - R1 = b[1] * ca[5] + b[2] * ca[4]; - R = fabs(c[6] * ad[0] - c[0] * ad[2]); - break; - case 10: - // A1 x B1 - R0 = a[0] * ca[7] + a[2] * ca[1]; - R1 = b[0] * ca[5] + b[2] * ca[3]; - R = fabs(c[7] * ad[0] - c[1] * ad[2]); - break; - case 11: - // A1 x B2 - R0 = a[0] * ca[8] + a[2] * ca[2]; - R1 = b[0] * ca[4] + b[1] * ca[3]; - R = fabs(c[8] * ad[0] - c[2] * ad[2]); - break; - case 12: - // A2 x B0 - R0 = a[0] * ca[3] + a[1] * ca[0]; - R1 = b[1] * ca[8] + b[2] * ca[7]; - R = fabs(c[0] * ad[1] - c[3] * ad[0]); - break; - case 13: - // A2 x B1 - R0 = a[0] * ca[4] + a[1] * ca[1]; - R1 = b[0] * ca[8] + b[2] * ca[6]; - R = fabs(c[1] * ad[1] - c[4] * ad[0]); - break; - case 14: - // A2 x B2 - R0 = a[0] * ca[5] + a[1] * ca[2]; - R1 = b[0] * ca[7] + b[1] * ca[6]; - R = fabs(c[2] * ad[1] - c[5] * ad[0]); - break; - default: - debugAssertM(false, "fell through switch statement"); - } - - return (R - (R0 + R1)); -} - - -bool CollisionDetection::parallelAxisForSolidBoxSolidBox( - const double* ca, - const double epsilon, - int & axis1, - int & axis2) { - const double parallelDot = 1.0 - epsilon; - for (int i = 0; i < 9; i++) { - if (ca[i] >= parallelDot) { - axis1 = i / 3; - axis2 = i % 3; - return true; - } - } - return false; -} - - - - -void CollisionDetection::fillSolidBoxSolidBoxInfo( - const Box & box1, - const Box & box2, - Vector3 & a, - Vector3 & b, - Vector3 & D, - double* c, - double* ca, - double* ad, - double* bd) { - // length between center and each side of box1 and box2 - a = box1.extent() * 0.5; - b = box2.extent() * 0.5; - - // difference between centers of box1 and box2 - D = box2.center() - box1.center(); - - // store the value of all possible dot products between the - // axes of box1 and box2, c_{row, col} in the Eberly paper - // corresponds to c[row * 3 + col] for this 9 element array. - // - // c[] holds signed values, ca[] hold absolute values - for (int i = 0; i < 9; i++) { - c[i] = dot(box1.axis(i / 3), box2.axis(i % 3)); - ca[i] = fabs(c[i]); - } - - // store all possible dot products between the axes of box1 and D, - // as well as the axes of box2 and D - for (int i = 0; i < 3; i++) { - ad[i] = dot(box1.axis(i), D); - bd[i] = dot(box2.axis(i), D); - } -} - - - -bool CollisionDetection::conservativeBoxBoxTest( - const Vector3 & a, const Vector3 & b, const Vector3 & D) { - // do a quick bounding sphere test because it is relatively - // cheap, (three dot products, two sqrts, and a few others) - double boxRadius1 = a.magnitude(); - double boxRadius2 = b.magnitude(); - return (D.squaredMagnitude() < square(boxRadius1 + boxRadius2)); -} - - - - -bool CollisionDetection::fixedSolidBoxIntersectsFixedSolidBox( - const Box& box1, - const Box& box2, - const int lastSeparatingAxis) { - // for explanations of the variable please refer to the - // paper and fillSolidBoxSolidBoxInfo() - Vector3 a; - Vector3 b; - Vector3 D; - double c[9]; - double ca[9]; - double ad[3]; - double bd[3]; - - fillSolidBoxSolidBoxInfo(box1, box2, a, b, D, c, ca, ad, bd); - - int dummy1, dummy2; - bool parallelAxes = parallelAxisForSolidBoxSolidBox(ca, 0.00001, - dummy1, dummy2); - - // check the separating axis from the last time step - if (lastSeparatingAxis != -1 && - (lastSeparatingAxis < 6 || !parallelAxes)) { - double projectedDistance = projectedDistanceForSolidBoxSolidBox( - lastSeparatingAxis, a, b, D, c, ca, ad, bd); - - // the separating axis from the last time step is still - // valid, the boxes do not intersect - if (projectedDistance > 0.0) { - return false; - } - } - - // test if the boxes can be separated by a plane normal to - // any of the three axes of box1, any of the three axes of box2, - // or any of the 9 possible cross products of axes from box1 - // and box2 - for (int i = 0; i < 15; i++) { - // do not need to check edge-edge cases if any two of - // the axes are parallel - if (parallelAxes && i == 6) { - return true; - } - - double projectedDistance = - projectedDistanceForSolidBoxSolidBox(i, a, b, D, c, ca, ad, bd); - - // found a separating axis, the boxes do not intersect - if (projectedDistance > 0.0) { - return false; - } - } - - return true; -} - - - -void CollisionDetection::closestPointsBetweenLineAndLine( - const Line & line1, - const Line & line2, - Vector3 & closest1, - Vector3 & closest2) { - // TODO make accessors for Line that don't make a copy of data - Vector3 P0 = line1.point(); - Vector3 u = line1.direction(); - Vector3 Q0 = line2.point(); - Vector3 v = line2.direction(); - Vector3 w0 = P0 - Q0; - - // a = 1.0, c = 1.0 - double b = dot(u, v); - double d = dot(u, w0); - double e = dot(v, w0); - double D = 1.0 - b * b; - double sc, tc; - - static const double epsilon = 0.00001; - - if (D < epsilon) { - // lines are parallel, choose P0 as one point, find the point - // on line2 that is closest to P0 - sc = 0.0; - tc = (b > 1.0) ? (d / b) : (e / 1.0); - } else { - // lines are not parallel - sc = (b * e - 1.0 * d) / D; - tc = (1.0 * e - b * d) / D; - } - - closest1 = P0 + (sc * u); - closest2 = Q0 + (tc * v); -} - - - -float CollisionDetection::penetrationDepthForFixedBoxFixedBox( - const Box& box1, - const Box& box2, - Array<Vector3>& contactPoints, - Array<Vector3>& contactNormals, - const int lastSeparatingAxis) { - - contactPoints.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - contactNormals.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - - Vector3 a; - Vector3 b; - Vector3 D; - double c[9]; - double ca[9]; - double ad[3]; - double bd[3]; - - debugAssert(lastSeparatingAxis >= -1); - debugAssert(lastSeparatingAxis < 15); - - fillSolidBoxSolidBoxInfo(box1, box2, a, b, D, c, ca, ad, bd); - - int axis1, axis2; - bool parallelAxes = parallelAxisForSolidBoxSolidBox(ca, 0.00001, - axis1, axis2); - - - // check the separating axis from the last time step - if (lastSeparatingAxis != -1 && - (lastSeparatingAxis < 6 || !parallelAxes)) { - float projectedDistance = projectedDistanceForSolidBoxSolidBox( - lastSeparatingAxis, a, b, D, c, ca, ad, bd); - - // the separating axis from the last time step is still - // valid, the boxes do not intersect - if (projectedDistance > 0.0) { - return -projectedDistance; - } - } - - // test if the boxes can be separated by a plane normal to - // any of the three axes of box1, any of the three axes of box2, - // (test 9 possible cross products later) - float penetration = -finf(); - int penetrationAxisIndex = -1; - - for (int i = 0; i < 6; i++) { - float projectedDistance = - projectedDistanceForSolidBoxSolidBox(i, a, b, D, c, ca, ad, bd); - - // found a separating axis, the boxes do not intersect - if (projectedDistance > 0.0) { - return -projectedDistance; - } - - // keep track of the axis that is least violated - if (projectedDistance > penetration) { - penetration = projectedDistance; - penetrationAxisIndex = i; - } - } - - - // for each edge-edge case we have to adjust the magnitude of - // penetration since we did not include the dot(L, L) denominator - // that can be smaller than 1.0 for the edge-edge cases. - if (!parallelAxes) { - double edgeDistances[9]; - - // run through edge-edge cases to see if we can find a separating axis - for (int i = 6; i < 15; i++) { - float projectedDistance = - projectedDistanceForSolidBoxSolidBox(i, a, b, D, c, ca, ad, bd); - - // found a separating axis, the boxes do not intersect, - // correct magnitude and return projected distance - if (projectedDistance > 0.0) { - Vector3 L = separatingAxisForSolidBoxSolidBox(i, box1, box2); - projectedDistance /= dot(L, L); - return -projectedDistance; - } - - edgeDistances[i - 6] = projectedDistance; - } - - // no separating axis found, the boxes do intersect, - // correct the magnitudes of the projectedDistance values - for (int i = 6; i < 15; i++) { - // find the negative penetration value with the smallest magnitude, - // the adjustment done for the edge-edge cases only increases - // magnitude by dividing by a number smaller than 1 and greater than 0 - float projectedDistance = (float)edgeDistances[i - 6]; - if (projectedDistance > penetration) { - Vector3 L = separatingAxisForSolidBoxSolidBox(i, box1, box2); - projectedDistance /= dot(L, L); - if (projectedDistance > penetration) { - penetration = projectedDistance; - penetrationAxisIndex = i; - } - } - } - } - - // get final separating axis vector - Vector3 L = separatingAxisForSolidBoxSolidBox(penetrationAxisIndex, - box1, box2); - - // set L to be the normal that faces away from box1 - if (dot(L, D) < 0) { - L = -L; - } - - Vector3 contactPoint; - - if (penetrationAxisIndex < 6) { - // vertex to face collision, find deepest colliding vertex - const Box* vertexBox; - const Box* faceBox; - Vector3 faceNormal = L; - - // L will be the outward facing normal for the faceBox - if (penetrationAxisIndex < 3) { - faceBox = & box1; - vertexBox = & box2; - if (dot(L, D) < 0) { - faceNormal = -L; - } - } else { - faceBox = & box2; - vertexBox = & box1; - if (dot(L, D) > 0) { - faceNormal = -L; - } - } - - // find the vertex that is farthest away in the direction - // face normal direction - int deepestPointIndex = 0; - float deepestPointDot = dot(faceNormal, vertexBox->corner(0)); - for (int i = 1; i < 8; i++) { - float dotProduct = dot(faceNormal, vertexBox->corner(i)); - if (dotProduct < deepestPointDot) { - deepestPointDot = dotProduct; - deepestPointIndex = i; - } - } - - // return the point half way between the deepest point and the - // contacting face - contactPoint = vertexBox->corner(deepestPointIndex) + - (-penetration * 0.5 * faceNormal); - } else { - // edge-edge case, find the two ege lines - int edge1 = (penetrationAxisIndex - 6) / 3; - int edge2 = (penetrationAxisIndex - 6) % 3; - Vector3 linePoint1 = box1.center(); - Vector3 linePoint2 = box2.center(); - Vector3 lineDir1; - Vector3 lineDir2; - - // find edge line by finding the edge axis, and the - // other two axes that are closest to the other box - for (int i = 0; i < 3; i++ ) { - if (i == edge1) { - lineDir1 = box1.axis(i); - } else { - Vector3 axis = box1.axis(i); - if (dot(axis, L) < 0) { - axis = -axis; - } - linePoint1 += axis * a[i]; - } - - if (i == edge2) { - lineDir2 = box2.axis(i); - } else { - Vector3 axis = box2.axis(i); - if (dot(axis, L) > 0) { - axis = -axis; - } - linePoint2 += axis * b[i]; - } - } - - // make lines from the two closest edges, and find - // the points that on each line that are closest to the other - Line line1 = Line::fromPointAndDirection(linePoint1, lineDir1); - Line line2 = Line::fromPointAndDirection(linePoint2, lineDir2); - Vector3 closest1; - Vector3 closest2; - - closestPointsBetweenLineAndLine(line1, line2, closest1, closest2); - - // take the average of the two closest edge points for the final - // contact point - contactPoint = (closest1 + closest2) * 0.5; - } - - contactPoints.push(contactPoint); - contactNormals.push(L); - - return -penetration; - -} - - - - -float CollisionDetection::penetrationDepthForFixedSphereFixedBox( - const Sphere& sphere, - const Box& box, - Array<Vector3>& contactPoints, - Array<Vector3>& contactNormals) { - - contactPoints.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - contactNormals.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - - // In its local coordinate frame, the box measures - // 2 * halfExtent[a] along dimesion a. - Vector3 halfExtent(box.extent(0), box.extent(1), box.extent(2)); - halfExtent *= 0.5f; - - CoordinateFrame boxFrame; - box.getLocalFrame(boxFrame); - - // Transform the sphere to the box's coordinate frame. - Vector3 center = boxFrame.pointToObjectSpace(sphere.center); - - // Find the square of the distance from the sphere to the box - - - // Distance along each axis from the closest side of the box - // to the sphere center. Negative values are *inside* the box. - Vector3 distOutsideBox; - - // Divide space up into the 27 regions corresponding - // to {+|-|0}X, {+|-|0}Y, {+|-|0}Z and classify the - // sphere center into one of them. - Vector3 centerRegion; - - // In the edge collision case, the edge is between vertices - // (constant + variable) and (constant - variable). - Vector3 constant, variable; - - int numNonZero = 0; - - // Iterate over axes - for (int a = 0; a < 3; ++a) { - // For each (box side), see which direction the sphere - // is outside the box (positive or negative). Add the - // square of that distance to the total distance from - // the box. - - float distanceFromLow = -halfExtent[a] - center[a]; - float distanceFromHigh = center[a] - halfExtent[a]; - - if (fabsf(distanceFromLow) < fabsf(distanceFromHigh)) { - distOutsideBox[a] = distanceFromLow; - } else { - distOutsideBox[a] = distanceFromHigh; - } - - if (distanceFromLow < 0.0) { - if (distanceFromHigh < 0.0) { - // Inside the box - centerRegion[a] = 0.0; - variable[a] = 1.0; - } else { - // Off the high side - centerRegion[a] = 1.0; - constant[a] = halfExtent[a]; - ++numNonZero; - } - } else if (distanceFromHigh < 0.0) { - // Off the low side - centerRegion[a] = -1.0; - constant[a] = -halfExtent[a]; - ++numNonZero; - } else { - debugAssertM(false, - "distanceFromLow and distanceFromHigh cannot both be positive"); - } - } - - // Squared distance between the outside of the box and the - // sphere center. - float d2 = Vector3::zero().max(distOutsideBox).squaredMagnitude(); - - if (d2 > square(sphere.radius)) { - // There is no penetration because the distance is greater - // than the radius of the sphere. This is the common case - // and we quickly exit. - return -1; - } - - // We know there is some penetration but need to classify it. - // - // Examine the region that contains the center of the sphere. If - // there is exactly one non-zero axis, the collision is with a - // plane. If there are exactly two non-zero axes, the collision - // is with an edge. If all three axes are non-zero, the collision is - // with a vertex. If there are no non-zero axes, the center is inside - // the box. - - double depth = -1; - switch (numNonZero) { - case 3: // Vertex collision - // The collision point is the vertex at constant, the normal - // is the vector from there to the sphere center. - contactNormals.append(boxFrame.normalToWorldSpace(constant - center)); - contactPoints.append(boxFrame.pointToWorldSpace(constant)); - depth = sphere.radius - sqrt(d2); - break; - - case 2: // Edge collision - { - // TODO: unwrapping the edge constructor and closest point - // code will probably make it faster. - - // Determine the edge - Line line = Line::fromPointAndDirection(constant, variable); - - // Penetration depth: - depth = sphere.radius - sqrt(d2); - - // The contact point is the closes point to the sphere on the line - Vector3 X = line.closestPoint(center); - contactNormals.append(boxFrame.normalToWorldSpace(X - center).direction()); - contactPoints.append(boxFrame.pointToWorldSpace(X)); - } - break; - - case 1: // Plane collision - { - // The plane normal is the centerRegion vector, - // so the sphere normal is the negative. Take - // it to world space from box-space. - - // Center region doesn't need to be normalized because - // it is known to contain only one non-zero value - // and that value is +/- 1. - Vector3 N = boxFrame.normalToWorldSpace(-centerRegion); - contactNormals.append(N); - - // Penetration depth: - depth = sphere.radius - sqrtf(d2); - - // Compute the contact point from the penetration depth - contactPoints.append(sphere.center + N * (sphere.radius - depth)); - } - break; - - case 0: // Volume collision - - // The sphere center is inside the box. This is an easy case - // to handle. Note that all axes of distOutsideBox must - // be negative. - - // Arbitratily choose the sphere center as a contact point - contactPoints.append(sphere.center); - - // Find the least-negative penetration axis. - // - // We could have computed this during the loop over the axes, - // but since volume collisions are rare (they only occur with - // large time steps), this case will seldom be executed and - // should not be optimized at the expense of the others. - if (distOutsideBox.x > distOutsideBox.y) { - if (distOutsideBox.x > distOutsideBox.z) { - // Smallest penetration on x-axis - // Chose normal based on which side we're closest to. - // Keep in mind that this is a normal to the sphere, - // so it is the inverse of the box normal. - if (center.x > 0) { - contactNormals.append(boxFrame.normalToWorldSpace(-Vector3::unitX())); - } else { - contactNormals.append(boxFrame.normalToWorldSpace(Vector3::unitX())); - } - depth = -distOutsideBox.x; - } else { - // Smallest penetration on z-axis - goto ZAXIS; - } - } else if (distOutsideBox.y > distOutsideBox.z) { - // Smallest penetration on y-axis - // Chose normal based on which side we're closest to. - // Keep in mind that this is a normal to the sphere, - // so it is the inverse of the box normal. - if (center.y > 0) { - contactNormals.append(boxFrame.normalToWorldSpace(-Vector3::unitY())); - } else { - contactNormals.append(boxFrame.normalToWorldSpace(Vector3::unitY())); - } - depth = -distOutsideBox.y; - } else { - // Smallest on z-axis -ZAXIS: - // Chose normal based on which side we're closest to. - // Keep in mind that this is a normal to the sphere, - // so it is the inverse of the box normal. - if (center.z > 0) { - contactNormals.append(boxFrame.normalToWorldSpace(-Vector3::unitZ())); - } else { - contactNormals.append(boxFrame.normalToWorldSpace(Vector3::unitZ())); - } - depth = -distOutsideBox.z; - } - break; - - default: - debugAssertM(false, "Fell through switch"); - break; - } - - return depth; -} - - -float CollisionDetection::penetrationDepthForFixedSphereFixedSphere( - const Sphere& sphereA, - const Sphere& sphereB, - Array<Vector3>& contactPoints, - Array<Vector3>& contactNormals) { - - Vector3 axis = sphereB.center - sphereA.center; - double radius = sphereA.radius + sphereB.radius; - double mag = axis.magnitude(); - axis /= mag; - double depth = -(mag - radius); - - contactPoints.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - contactNormals.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - - if (depth >= 0) { - contactPoints.append(sphereA.center + axis * (sphereA.radius - depth / 2)); - contactNormals.append(axis); - } - - return depth; -} - - -float CollisionDetection::penetrationDepthForFixedSphereFixedPlane( - const Sphere& sphereA, - const Plane& planeB, - Array<Vector3>& contactPoints, - Array<Vector3>& contactNormals) { - - Vector3 N; - double d; - - planeB.getEquation(N, d); - - double depth = -(sphereA.center.dot(N) + d - sphereA.radius); - - contactPoints.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - contactNormals.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - - if (depth >= 0) { - contactPoints.append(N * (depth - sphereA.radius) + sphereA.center); - contactNormals.append(N); - } - - return depth; -} - - -float CollisionDetection::penetrationDepthForFixedBoxFixedPlane( - const Box& box, - const Plane& plane, - Array<Vector3>& contactPoints, - Array<Vector3>& contactNormals) { - - Vector3 N; - double d; - - plane.getEquation(N, d); - - contactPoints.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - contactNormals.resize(0, DONT_SHRINK_UNDERLYING_ARRAY); - - float lowest = finf(); - for (int i = 0; i < 8; ++i) { - const Vector3 vertex = box.corner(i); - - float x = vertex.dot(N) + (float)d; - - if (x <= 0) { - // All vertices below the plane should be contact points. - contactPoints.append(vertex); - contactNormals.append(-N); - } - - lowest = min(lowest, x); - } - - // Depth should be a positive number - return -lowest; -} - - -float CollisionDetection::collisionTimeForMovingPointFixedPlane( - const Vector3& point, - const Vector3& velocity, - const Plane& plane, - Vector3& location, - Vector3& outNormal) { - - // Solve for the time at which normal.dot(point + velocity) + d == 0. - double d; - Vector3 normal; - plane.getEquation(normal, d); - - float vdotN = velocity.dot(normal); - float pdotN = point.dot(normal); - - if (fuzzyEq(pdotN + d, 0)) { - // The point is *in* the plane. - location = point; - outNormal = normal; - return 0; - } - - if (vdotN >= 0) { - // no collision will occur - location = Vector3::inf(); - return finf(); - } - - float t = -(pdotN + d) / vdotN; - if (t < 0) { - location = Vector3::inf(); - return finf(); - } else { - location = point + velocity * t; - outNormal = normal; - return t; - } -} - -bool __fastcall CollisionDetection::rayAABox( - const Ray& ray, - const Vector3& invDir, - const AABox& box, - const Vector3& boxCenter, - float boundingRadiusSquared, - Vector3& location, - bool& inside) { - - debugAssertM(fabs(ray.direction().squaredLength() - 1.0f) < 0.01f, format("Length = %f", ray.direction().length())); - { - // Pre-emptive partial bounding sphere test - const Vector3 L(boxCenter - ray.origin()); - float d = L.dot(ray.direction()); - - float L2 = L.dot(L); - float D2 = square(d); - float M2 = L2 - D2; - - if (((d < 0) && (L2 > boundingRadiusSquared)) || (M2 > boundingRadiusSquared)) { - inside = false; - return false; - } - // Passing here does not mean that the ray hits the bounding sphere; - // we would still have to perform more expensive tests to determine - // that. - } - - inside = true; - const Vector3& MinB = box.low(); - const Vector3& MaxB = box.high(); - Vector3 MaxT(-1.0f, -1.0f, -1.0f); - - // Find candidate planes. - for (int i = 0; i < 3; ++i) { - if (ray.origin()[i] < MinB[i]) { - location[i] = MinB[i]; - inside = false; - - // Calculate T distances to candidate planes - if (ray.direction()[i] != 0) { - MaxT[i] = (MinB[i] - ray.origin()[i]) * invDir[i]; - } - } else if (ray.origin()[i] > MaxB[i]) { - location[i] = MaxB[i]; - inside = false; - - // Calculate T distances to candidate planes - if (ray.direction()[i] != 0) { - MaxT[i] = (MaxB[i] - ray.origin()[i]) * invDir[i]; - } - } - } - - if (inside) { - // Ray origin inside bounding box - location = ray.origin(); - return true; - } - - // Get largest of the maxT's for final choice of intersection - int WhichPlane = 0; - if (MaxT[1] > MaxT[WhichPlane]) { - WhichPlane = 1; - } - - if (MaxT[2] > MaxT[WhichPlane]) { - WhichPlane = 2; - } - - // Check final candidate actually inside box - if (MaxT[WhichPlane] < 0.0f) { - // Miss the box - return false; - } - - for (int i = 0; i < 3; ++i) { - if (i != WhichPlane) { - location[i] = ray.origin()[i] + MaxT[WhichPlane] * ray.direction()[i]; - if ((location[i] < MinB[i]) || - (location[i] > MaxB[i])) { - // On this plane we're outside the box extents, so - // we miss the box - return false; - } - } - } - - return true; -} - -float CollisionDetection::collisionTimeForMovingPointFixedSphere( - const Vector3& point, - const Vector3& velocity, - const Sphere& sphere, - Vector3& location, - Vector3& outNormal, - bool solid) { - - if (solid && sphere.contains(point)) { - location = point; - outNormal = (point - sphere.center).direction(); - return 0.0f; - } - - float speed = velocity.magnitude(); - const Vector3& direction = velocity / speed; - - // length of the axis between the start and the sphere - const Vector3& L = sphere.center - point; - float d = L.dot(direction); - - float L2 = L.dot(L); - float R2 = square(sphere.radius); - float D2 = square(d); - - if ((d < 0.0f) && (L2 > R2)) { - location = Vector3::inf(); - return finf(); - } - - const float M2 = L2 - D2; - - if (M2 > R2) { - location = Vector3::inf(); - return finf(); - } - - float q = sqrt(R2 - M2); - float time; - - if (L2 > R2) { - time = d - q; - } else { - time = d + q; - } - - time /= speed; - - location = point + velocity * time; - outNormal = (location - sphere.center).direction(); - - return time; -} - - -float CollisionDetection::collisionTimeForMovingSphereFixedSphere( - const Sphere& movingSphere, - const Vector3& velocity, - const Sphere& fixedSphere, - Vector3& location, - Vector3& outNormal) { - - const Vector3& sep = (fixedSphere.center - movingSphere.center); - float sepLen = sep.squaredLength(); - if (sepLen < square(movingSphere.radius + fixedSphere.radius)) { - // Interpenetrating - outNormal = sep.directionOrZero(); - location = fixedSphere.center - outNormal * fixedSphere.radius; - return 0; - } - - float time = collisionTimeForMovingPointFixedSphere - (movingSphere.center, velocity, - Sphere(fixedSphere.center, fixedSphere.radius + movingSphere.radius), - location, outNormal); - - if (time < finf()) { - // Location is now the center of the moving sphere at the collision time. - // Adjust for the size of the moving sphere. Two spheres always collide - // along a line between their centers. - location += (location - fixedSphere.center) * movingSphere.radius / fixedSphere.radius; - } - - return time; -} - - -/* -float CollisionDetection::collisionTimeForMovingPointFixedTriangle( - const Vector3& point, - const Vector3& velocity, - const Triangle& triangle, - Vector3& outLocation, - Vector3& outNormal) { - - double time = collisionTimeForMovingPointFixedPlane(point, velocity, triangle.plane(), outLocation, outNormal); - - if (time == finf()) { - // No collision with the plane of the triangle. - return finf(); - } - - if (isPointInsideTriangle(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.normal(), outLocation, triangle.primaryAxis())) { - // Collision occured inside the triangle - return time; - } else { - // Missed the triangle - outLocation = Vector3::inf(); - return finf(); - } -}*/ - -/* -float CollisionDetection::collisionTimeForMovingPointFixedTriangle( - const Vector3& orig, - const Vector3& dir, - const Vector3& vert0, - const Vector3& vert1, - const Vector3& vert2) { - - // Barycenteric coords - double u, v; - #define EPSILON 0.000001 - #define CROSS(dest,v1,v2) \ - dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \ - dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \ - dest[2]=v1[0]*v2[1]-v1[1]*v2[0]; - - #define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]) - - #define SUB(dest,v1,v2) \ - dest[0]=v1[0]-v2[0]; \ - dest[1]=v1[1]-v2[1]; \ - dest[2]=v1[2]-v2[2]; - - double edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; - - // find vectors for two edges sharing vert0 - SUB(edge1, vert1, vert0); - SUB(edge2, vert2, vert0); - - // begin calculating determinant - also used to calculate U parameter - CROSS(pvec, dir, edge2); - - // if determinant is near zero, ray lies in plane of triangle - const double det = DOT(edge1, pvec); - - if (det < EPSILON) { - return finf(); - } - - // calculate distance from vert0 to ray origin - SUB(tvec, orig, vert0); - - // calculate U parameter and test bounds - u = DOT(tvec, pvec); - if ((u < 0.0) || (u > det)) { - // Hit the plane outside the triangle - return finf(); - } - - // prepare to test V parameter - CROSS(qvec, tvec, edge1); - - // calculate V parameter and test bounds - v = DOT(dir, qvec); - if ((v < 0.0) || (u + v > det)) { - // Hit the plane outside the triangle - return finf(); - } - - // calculate t, scale parameters, ray intersects triangle - // If we want u,v, we can compute this - // double t = DOT(edge2, qvec); - //const double inv_det = 1.0 / det; - //t *= inv_det; - //u *= inv_det; - //v *= inv_det; - // return t; - - // Case where we don't need correct (u, v): - - const double t = DOT(edge2, qvec); - - if (t >= 0) { - // Note that det must be positive - return t / det; - } else { - // We had to travel backwards in time to intersect - return finf(); - } - - #undef EPSILON - #undef CROSS - #undef DOT - #undef SUB -} -*/ - -float CollisionDetection::collisionTimeForMovingPointFixedBox( - const Vector3& point, - const Vector3& velocity, - const Box& box, - Vector3& location, - Vector3& outNormal) { - - double bestTime; - - Vector3 normal; - Vector3 v[4]; - - // Prime the loop - int f = 0; - box.getFaceCorners(f, v[0], v[1], v[2], v[3]); - bestTime = collisionTimeForMovingPointFixedRectangle(point, velocity, v[0], v[1], v[2], v[3], location, normal); - outNormal = normal; - - // Check other faces - for (f = 1; f < 6; ++f) { - Vector3 pos; - box.getFaceCorners(f, v[0], v[1], v[2], v[3]); - float time = collisionTimeForMovingPointFixedRectangle(point, velocity, v[0], v[1], v[2], v[3], pos, normal); - if (time < bestTime) { - bestTime = time; - outNormal = normal; - location = pos; - } - } - - return bestTime; -} - - -float CollisionDetection::collisionTimeForMovingPointFixedAABox( - const Vector3& origin, - const Vector3& dir, - const AABox& box, - Vector3& location, - bool& Inside, - Vector3& normal) { - - if (collisionLocationForMovingPointFixedAABox(origin, dir, box, location, Inside, normal)) { - return (location - origin).magnitude(); - } else { - return (float)finf(); - } -} - - -bool CollisionDetection::collisionLocationForMovingPointFixedAABox( - const Vector3& origin, - const Vector3& dir, - const AABox& box, - Vector3& location, - bool& Inside, - Vector3& normal) { - - // Integer representation of a floating-point value. - #define IR(x) ((uint32&)x) - - Inside = true; - const Vector3& MinB = box.low(); - const Vector3& MaxB = box.high(); - Vector3 MaxT(-1.0f, -1.0f, -1.0f); - - // Find candidate planes. - for (int i = 0; i < 3; ++i) { - if (origin[i] < MinB[i]) { - location[i] = MinB[i]; - Inside = false; - - // Calculate T distances to candidate planes - if (IR(dir[i])) { - MaxT[i] = (MinB[i] - origin[i]) / dir[i]; - } - } else if (origin[i] > MaxB[i]) { - location[i] = MaxB[i]; - Inside = false; - - // Calculate T distances to candidate planes - if (IR(dir[i])) { - MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; - } - } - } - - if (Inside) { - // Ray origin inside bounding box - location = origin; - return false; - } - - // Get largest of the maxT's for final choice of intersection - int WhichPlane = 0; - if (MaxT[1] > MaxT[WhichPlane]) { - WhichPlane = 1; - } - - if (MaxT[2] > MaxT[WhichPlane]) { - WhichPlane = 2; - } - - // Check final candidate actually inside box - if (IR(MaxT[WhichPlane]) & 0x80000000) { - // Miss the box - return false; - } - - for (int i = 0; i < 3; ++i) { - if (i != WhichPlane) { - location[i] = origin[i] + MaxT[WhichPlane] * dir[i]; - if ((location[i] < MinB[i]) || - (location[i] > MaxB[i])) { - // On this plane we're outside the box extents, so - // we miss the box - return false; - } - } - } - - // Choose the normal to be the plane normal facing into the ray - normal = Vector3::zero(); - normal[WhichPlane] = (dir[WhichPlane] > 0) ? -1.0 : 1.0; - - return true; - - #undef IR -} - - - -float CollisionDetection::collisionTimeForMovingPointFixedRectangle( - const Vector3& point, - const Vector3& velocity, - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& v3, - Vector3& location, - Vector3& outNormal) { - - Plane plane = Plane(v0, v1, v2); - - float time = collisionTimeForMovingPointFixedPlane(point, velocity, plane, location, outNormal); - - if (time == finf()) { - // No collision is ever going to happen - return time; - } - - if (isPointInsideRectangle(v0, v1, v2, v3, plane.normal(), location)) { - // The intersection point is inside the rectangle; that is the location where - // the point hits the rectangle. - return time; - } else { - return finf(); - } -} - -/** Used by findRayCapsuleIntersection. - @cite From magic software http://www.magic-software.com/Source/Intersection3D/MgcIntr3DLinCap.cpp */ -static int findRayCapsuleIntersectionAux( - const Vector3& rkOrigin, - const Vector3& rkDirection, - const Capsule& rkCapsule, - double afT[2]) { - - Vector3 capsuleDirection = rkCapsule.point(1) - rkCapsule.point(0); - - // set up quadratic Q(t) = a*t^2 + 2*b*t + c - Vector3 kU, kV, kW = capsuleDirection; - float fWLength = kW.unitize(); - Vector3::generateOrthonormalBasis(kU, kV, kW); - Vector3 kD(kU.dot(rkDirection), kV.dot(rkDirection), kW.dot(rkDirection)); - float fDLength = kD.unitize(); - - float fEpsilon = 1e-6f; - - float fInvDLength = 1.0f/fDLength; - Vector3 kDiff = rkOrigin - rkCapsule.point(0); - Vector3 kP(kU.dot(kDiff),kV.dot(kDiff),kW.dot(kDiff)); - float fRadiusSqr = square(rkCapsule.radius()); - - float fInv, fA, fB, fC, fDiscr, fRoot, fT, fTmp; - - // Is the velocity parallel to the capsule direction? (or zero) - if ((abs(kD.z) >= 1.0f - fEpsilon) || (fDLength < fEpsilon)) { - - float fAxisDir = rkDirection.dot(capsuleDirection); - - fDiscr = fRadiusSqr - kP.x*kP.x - kP.y*kP.y; - if ((fAxisDir < 0) && (fDiscr >= 0.0f)) { - // Velocity anti-parallel to the capsule direction - fRoot = sqrt(fDiscr); - afT[0] = (kP.z + fRoot)*fInvDLength; - afT[1] = -(fWLength - kP.z + fRoot)*fInvDLength; - return 2; - } else if ((fAxisDir > 0) && (fDiscr >= 0.0f)) { - // Velocity parallel to the capsule direction - fRoot = sqrt(fDiscr); - afT[0] = -(kP.z + fRoot)*fInvDLength; - afT[1] = (fWLength - kP.z + fRoot)*fInvDLength; - return 2; - } else { - // sphere heading wrong direction, or no velocity at all - return 0; - } - } - - // test intersection with infinite cylinder - fA = kD.x*kD.x + kD.y*kD.y; - fB = kP.x*kD.x + kP.y*kD.y; - fC = kP.x*kP.x + kP.y*kP.y - fRadiusSqr; - fDiscr = fB*fB - fA*fC; - if (fDiscr < 0.0f) { - // line does not intersect infinite cylinder - return 0; - } - - int iQuantity = 0; - - if (fDiscr > 0.0f) { - // line intersects infinite cylinder in two places - fRoot = sqrt(fDiscr); - fInv = 1.0f/fA; - fT = (-fB - fRoot)*fInv; - fTmp = kP.z + fT*kD.z; - if ((0.0f <= fTmp) && (fTmp <= fWLength)) { - afT[iQuantity] = fT * fInvDLength; - iQuantity++; - } - - fT = (-fB + fRoot)*fInv; - fTmp = kP.z + fT*kD.z; - - if ((0.0f <= fTmp) && (fTmp <= fWLength)) { - afT[iQuantity++] = fT*fInvDLength; - } - - if (iQuantity == 2) { - // line intersects capsule wall in two places - return 2; - } - } else { - // line is tangent to infinite cylinder - fT = -fB/fA; - fTmp = kP.z + fT*kD.z; - if ((0.0f <= fTmp) && (fTmp <= fWLength)) { - afT[0] = fT*fInvDLength; - return 1; - } - } - - // test intersection with bottom hemisphere - // fA = 1 - fB += kP.z*kD.z; - fC += kP.z*kP.z; - fDiscr = fB*fB - fC; - if (fDiscr > 0.0f) { - fRoot = sqrt(fDiscr); - fT = -fB - fRoot; - fTmp = kP.z + fT*kD.z; - if (fTmp <= 0.0f) { - afT[iQuantity++] = fT*fInvDLength; - if (iQuantity == 2) { - return 2; - } - } - - fT = -fB + fRoot; - fTmp = kP.z + fT*kD.z; - if (fTmp <= 0.0f) { - afT[iQuantity++] = fT*fInvDLength; - if (iQuantity == 2) { - return 2; - } - } - } else if (fDiscr == 0.0f) { - fT = -fB; - fTmp = kP.z + fT*kD.z; - if (fTmp <= 0.0f) { - afT[iQuantity++] = fT*fInvDLength; - if (iQuantity == 2) { - return 2; - } - } - } - - // test intersection with top hemisphere - // fA = 1 - fB -= kD.z*fWLength; - fC += fWLength*(fWLength - 2.0f*kP.z); - - fDiscr = fB*fB - fC; - if (fDiscr > 0.0f) { - fRoot = sqrt(fDiscr); - fT = -fB - fRoot; - fTmp = kP.z + fT*kD.z; - if (fTmp >= fWLength) { - afT[iQuantity++] = fT*fInvDLength; - if (iQuantity == 2) { - return 2; - } - } - - fT = -fB + fRoot; - fTmp = kP.z + fT*kD.z; - if (fTmp >= fWLength) { - afT[iQuantity++] = fT*fInvDLength; - if (iQuantity == 2) { - return 2; - } - } - } else if (fDiscr == 0.0f) { - fT = -fB; - fTmp = kP.z + fT*kD.z; - if (fTmp >= fWLength) { - afT[iQuantity++] = fT*fInvDLength; - if (iQuantity == 2) { - return 2; - } - } - } - - return iQuantity; -} - - -/** Used by collisionTimeForMovingPointFixedCapsule. - @cite From magic software http://www.magic-software.com/Source/Intersection3D/MgcIntr3DLinCap.cpp - - @param rkRay The ray - @param rkCapsule The capsule - @param riQuantity The number of intersections found - @param akPoint The intersections found - @return True if there is at least one intersection - */ -static bool findRayCapsuleIntersection( - const Ray& rkRay, - const Capsule& rkCapsule, - int& riQuantity, - Vector3 akPoint[2]) { - - double afT[2]; - riQuantity = findRayCapsuleIntersectionAux(rkRay.origin(), rkRay.direction(), rkCapsule, afT); - - // Only return intersections that occur in the future - int iClipQuantity = 0; - int i; - for (i = 0; i < riQuantity; ++i) { - if (afT[i] >= 0.0f) { - akPoint[iClipQuantity] = rkRay.origin() + afT[i] * rkRay.direction(); - ++iClipQuantity; - } - } - - riQuantity = iClipQuantity; - return (riQuantity > 0); -} - -float CollisionDetection::collisionTimeForMovingPointFixedCapsule( - const Vector3& _point, - const Vector3& velocity, - const Capsule& capsule, - Vector3& location, - Vector3& outNormal) { - - float timeScale = velocity.magnitude(); - - if (timeScale == 0.0f) { - timeScale = 1; - } - - Vector3 direction = velocity / timeScale; - int numIntersections; - Vector3 intersection[2]; - findRayCapsuleIntersection(Ray::fromOriginAndDirection(_point, direction), capsule, numIntersections, intersection); - - if (numIntersections == 2) { - // A collision can only occur if there are two intersections. If there is one - // intersection, that one is exiting the capsule. - - // Find the entering intersection (the first one that occurs). - float d0 = (intersection[0] - _point).squaredMagnitude(); - float d1 = (intersection[1] - _point).squaredMagnitude(); - - // Compute the surface normal (if we aren't ignoring the result) - if (&outNormal != &ignore) { - Vector3 p2 = LineSegment::fromTwoPoints(capsule.point(0), capsule.point(1)).closestPoint(_point); - outNormal = (_point - p2).direction(); - } - - if (d0 > d1) { - location = intersection[1]; - return sqrt(d1) / timeScale; - } else { - location = intersection[0]; - return sqrt(d0) / timeScale; - } - } else { - // No entering intersection discovered; return no intersection. - location = Vector3::inf(); - return finf(); - } -} - - -float CollisionDetection::collisionTimeForMovingSphereFixedPlane( - const Sphere& sphere, - const Vector3& velocity, - const Plane& plane, - Vector3& location, - Vector3& outNormal) { - - if (sphere.radius == 0) { - // Optimization for zero radius sphere - return collisionTimeForMovingPointFixedPlane(sphere.center, velocity, plane, location, outNormal); - } - - // The collision point on the sphere will be the point at - // center - (radius * normal). Collisions only occur when - // the sphere is travelling into the plane. - - double d; - plane.getEquation(outNormal, d); - - double vdotN = velocity.dot(outNormal); - - if (fuzzyGt(vdotN, 0)) { - // No collision when the sphere is moving towards a backface. - location = Vector3::inf(); - return (float)finf(); - } - - float cdotN = sphere.center.dot(outNormal); - - // Distance from the center to the plane - float distance = cdotN + (float)d; - - // Where is the collision on the sphere? - Vector3 point = sphere.center - (sphere.radius * outNormal); - - if (fuzzyLe(G3D::abs(distance), sphere.radius)) { - // Already interpenetrating - location = sphere.center - distance * outNormal; - return 0; - } else { - return collisionTimeForMovingPointFixedPlane(point, velocity, plane, location, outNormal); - } - -} - - -float CollisionDetection::collisionTimeForMovingSphereFixedTriangle( - const class Sphere& sphere, - const Vector3& velocity, - const Triangle& triangle, - Vector3& outLocation, - float b[3]) { - - Vector3 dummy; - float time = collisionTimeForMovingSphereFixedPlane(sphere, velocity, triangle.plane(), - outLocation, dummy); - - if (time == finf()) { - // No collision is ever going to happen - return time; - } - - // We will hit the plane of the triangle at *time*. See if - // the intersection point actually is within the triangle. - - if (isPointInsideTriangle(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.normal(), - outLocation, b, triangle.primaryAxis())) { - - // The intersection point is inside the triangle; that is the location where - // the sphere hits the triangle. - -# ifdef G3D_DEBUG - { - // Internal consistency checks - debugAssertM(b[0] >= 0.0 && b[0] <= 1.0f, "Intersection is outside triangle."); - debugAssertM(b[1] >= 0.0 && b[1] <= 1.0f, "Intersection is outside triangle."); - debugAssertM(b[2] >= 0.0 && b[2] <= 1.0f, "Intersection is outside triangle."); - Vector3 blend = - b[0] * triangle.vertex(0) + - b[1] * triangle.vertex(1) + - b[2] * triangle.vertex(2); - debugAssertM(blend.fuzzyEq(outLocation), "Barycentric coords don't match intersection."); - // Call again so that we can debug the problem - // isPointInsideTriangle(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.normal(), - // outLocation, b, triangle.primaryAxis()); - } -# endif - - return time; - } - - // The collision (if it exists) is with a point on the triangle perimeter. - // Switch over to moving the triangle towards a fixed sphere and see at what time - // they will hit. - - // Closest point on the triangle to the sphere intersection with the plane. - int edgeIndex; - const Vector3& point = closestPointOnTrianglePerimeter(triangle._vertex, triangle.edgeDirection, - triangle.edgeMagnitude, outLocation, edgeIndex); - - float t = 0; - if (! sphere.contains(point)) { - // The point is outside the sphere--see when it will hit - t = collisionTimeForMovingPointFixedSphere(point, -velocity, sphere, dummy, dummy); - } - - if (t < finf()) { - outLocation = point; - // Compute Barycentric coords - - // Index of the next vertex - static const int next[] = {1, 2, 0}; - - // Project along the edge in question. - // Avoid sqrt by taking advantage of the existing edgeDirection unit vector. - b[next[edgeIndex]] = (outLocation - triangle._vertex[edgeIndex]).dot - (triangle.edgeDirection[edgeIndex]) / triangle.edgeMagnitude[edgeIndex]; - - b[edgeIndex] = 1.0f - b[next[edgeIndex]]; - - b[next[next[edgeIndex]]] = 0.0f; - -# ifdef G3D_DEBUG - { - // Internal consistency checks - for (int i = 0; i < 3; ++i) { - debugAssertM(fuzzyGe(b[i], 0.0f) && fuzzyLe(b[i], 1.0f), "Intersection is outside triangle."); - } - Vector3 blend = - b[0] * triangle.vertex(0) + - b[1] * triangle.vertex(1) + - b[2] * triangle.vertex(2); - debugAssertM(blend.fuzzyEq(outLocation), - format("Barycentric coords don't match intersection. %s != %s", - blend.toString().c_str(), - outLocation.toString().c_str())); - - // Call again so that we can debug the problem - collisionTimeForMovingPointFixedSphere(point, -velocity, sphere, dummy, dummy); - } -# endif - - // Due to tiny roundoffs, these values might be slightly out of bounds. - // Ensure that they are legal. Note that the above debugging code - // verifies that we are not clamping truly illegal values. - for (int i = 0; i < 3; ++i) { - b[i] = clamp(b[i], 0.0f, 1.0f); - } - } - - // The collision occured at the point, if it occured. The normal - // was the plane normal, computed above. - - return t; -} - - -float CollisionDetection::collisionTimeForMovingSphereFixedRectangle( - const Sphere& sphere, - const Vector3& velocity, - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& v3, - Vector3& location, - Vector3& outNormal) { - - Plane plane(v0, v1, v2); - - float time = collisionTimeForMovingSphereFixedPlane(sphere, velocity, plane, location, outNormal); - - if (time == finf()) { - // No collision is ever going to happen - return time; - } - - if (isPointInsideRectangle(v0, v1, v2, v3, plane.normal(), location)) { - // The intersection point is inside the rectangle; that is the location where - // the sphere hits the rectangle. - return time; - } - - // Switch over to moving the rectangle towards a fixed sphere and see at what time - // they will hit. - - Vector3 point = closestPointToRectanglePerimeter(v0, v1, v2, v3, sphere.center); - - Vector3 dummy; - double t = collisionTimeForMovingPointFixedSphere(point, -velocity, sphere, location, dummy); - - // Normal is the plane normal, location is the original location of the point. - location = point; - - return t; -} - - -float CollisionDetection::collisionTimeForMovingSphereFixedBox( - const Sphere& sphere, - const Vector3& velocity, - const Box& box, - Vector3& location, - Vector3& outNormal) { - - if (fixedSolidSphereIntersectsFixedSolidBox(sphere, box)) { - // TODO: Compute more useful location and normal? - location = sphere.center; - outNormal = Vector3::zero(); - return 0; - } - - float bestTime; - - Vector3 v[4]; - int f = 0; - box.getFaceCorners(f, v[0], v[1], v[2], v[3]); - bestTime = collisionTimeForMovingSphereFixedRectangle(sphere, velocity, v[0], v[1], v[2], v[3], location, outNormal); - - for (f = 1; f < 6; ++f) { - Vector3 pos, normal; - box.getFaceCorners(f, v[0], v[1], v[2], v[3]); - float time = collisionTimeForMovingSphereFixedRectangle(sphere, velocity, v[0], v[1], v[2], v[3], pos, normal); - if (time < bestTime) { - bestTime = time; - location = pos; - outNormal = normal; - } - } - - return bestTime; -} - - -float CollisionDetection::collisionTimeForMovingSphereFixedCapsule( - const Sphere& sphere, - const Vector3& velocity, - const Capsule& capsule, - Vector3& location, - Vector3& outNormal) { - - (void)outNormal; - - Capsule _capsule(capsule.point(0), capsule.point(1), capsule.radius() + sphere.radius); - - Vector3 normal; - double time = collisionTimeForMovingPointFixedCapsule(sphere.center, velocity, _capsule, location, normal); - - if (time < finf()) { - // Location is now the position of the center of the sphere at the time of collision. - // We have to adjust the collision location for the size of the sphere. - location -= sphere.radius * normal; - } - - return time; -} - - -Vector3 CollisionDetection::bounceDirection( - const Sphere& sphere, - const Vector3& velocity, - const float collisionTime, - const Vector3& collisionLocation, - const Vector3& collisionNormal) { - - // Location when the collision occurs - Vector3 sphereLocation = sphere.center + velocity * collisionTime; - - Vector3 normal = (sphereLocation - collisionLocation); - if (fuzzyEq(normal.squaredMagnitude(), 0)) { - normal = collisionNormal; - } else { - normal.unitize(); - } - - Vector3 direction = velocity.direction(); - - // Reflect direction about the normal - return direction - 2.0 * normal * normal.dot(direction); -} - - -Vector3 CollisionDetection::slideDirection( - const Sphere& sphere, - const Vector3& velocity, - const float collisionTime, - const Vector3& collisionLocation) { - - Vector3 sphereLocation = sphere.center + velocity * collisionTime; - Vector3 normal = (sphereLocation - collisionLocation).direction(); - Vector3 direction = velocity.direction(); - - // subtract off the part in the direction away from the normal. - return direction - normal * normal.dot(direction); -} - - -Vector3 CollisionDetection::closestPointOnLineSegment( - const Vector3& v0, - const Vector3& v1, - const Vector3& point) { - - const Vector3& edge = (v1 - v0); - float edgeLength = edge.magnitude(); - - if (edgeLength == 0) { - // The line segment is a point - return v0; - } - - return closestPointOnLineSegment(v0, v1, edge / edgeLength, edgeLength, point); -} - - -Vector3 CollisionDetection::closestPointOnLineSegment( - const Vector3& v0, - const Vector3& v1, - const Vector3& edgeDirection, - const float edgeLength, - const Vector3& point) { - - debugAssert((v1 - v0).direction().fuzzyEq(edgeDirection)); - debugAssert(fuzzyEq((v1 - v0).magnitude(), edgeLength)); - - // Vector towards the point - const Vector3& c = point - v0; - - // Projected onto the edge itself - float t = edgeDirection.dot(c); - - if (t <= 0) { - // Before the start - return v0; - } else if (t >= edgeLength) { - // After the end - return v1; - } else { - // At distance t along the edge - return v0 + edgeDirection * t; - } -} - - -Vector3 CollisionDetection::closestPointOnTrianglePerimeter( - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& point) { - - Vector3 v[3] = {v0, v1, v2}; - Vector3 edgeDirection[3] = {(v1 - v0), (v2 - v1), (v0 - v2)}; - float edgeLength[3]; - - for (int i = 0; i < 3; ++i) { - edgeLength[i] = edgeDirection[i].magnitude(); - edgeDirection[i] /= edgeLength[i]; - } - - int edgeIndex; - return closestPointOnTrianglePerimeter(v, edgeDirection, edgeLength, point, edgeIndex); -} - - -Vector3 CollisionDetection::closestPointOnTrianglePerimeter( - const Vector3 v[3], - const Vector3 edgeDirection[3], - const float edgeLength[3], - const Vector3& point, - int& edgeIndex) { - - // Closest point on segment from v[i] to v[i + 1] - Vector3 r[3]; - - // Distance squared from r[i] to point - float d[3]; - - // Index of the next point - static const int next[] = {1, 2, 0}; - - for (int i = 0; i < 3; ++i) { - r[i] = closestPointOnLineSegment(v[i], v[next[i]], edgeDirection[i], edgeLength[i], point); - d[i] = (r[i] - point).squaredMagnitude(); - } - - if (d[0] < d[1]) { - if (d[0] < d[2]) { - // Between v0 and v1 - edgeIndex = 0; - } else { - // Between v2 and v0 - edgeIndex = 2; - } - } else { - if (d[1] < d[2]) { - // Between v1 and v2 - edgeIndex = 1; - } else { - // Between v2 and v0 - edgeIndex = 2; - } - } - -# ifdef G3D_DEBUG - { - Vector3 diff = r[edgeIndex] - v[edgeIndex]; - debugAssertM(fuzzyEq(diff.direction().dot(edgeDirection[edgeIndex]), 1.0f) || - diff.fuzzyEq(Vector3::zero()), "Point not on correct triangle edge"); - float frac = diff.dot(edgeDirection[edgeIndex])/edgeLength[edgeIndex]; - debugAssertM(frac >= -0.000001, "Point off low side of edge."); - debugAssertM(frac <= 1.000001, "Point off high side of edge."); - } -# endif - - return r[edgeIndex]; -} - - -bool CollisionDetection::isPointInsideTriangle( - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& normal, - const Vector3& point, - float b[3], - Vector3::Axis primaryAxis) { - - if (primaryAxis == Vector3::DETECT_AXIS) { - primaryAxis = normal.primaryAxis(); - } - - // Check that the point is within the triangle using a Barycentric - // coordinate test on a two dimensional plane. - int i, j; - - switch (primaryAxis) { - case Vector3::X_AXIS: - i = Vector3::Y_AXIS; - j = Vector3::Z_AXIS; - break; - - case Vector3::Y_AXIS: - i = Vector3::Z_AXIS; - j = Vector3::X_AXIS; - break; - - case Vector3::Z_AXIS: - i = Vector3::X_AXIS; - j = Vector3::Y_AXIS; - break; - - default: - // This case is here to supress a warning on Linux - i = j = 0; - debugAssertM(false, "Should not get here."); - break; - } - - // See if all barycentric coordinates are non-negative - - // 2D area via cross product -# define AREA2(d, e, f) (((e)[i] - (d)[i]) * ((f)[j] - (d)[j]) - ((f)[i] - (d)[i]) * ((e)[j] - (d)[j])) - - // Area of the polygon - float area = AREA2(v0, v1, v2); - if (area == 0) { - // This triangle has zero area, so the point must not - // be in it unless the triangle point is the test point. - return (v0 == point); - } - - debugAssert(area != 0); - - float invArea = 1.0f / area; - - // (avoid normalization until absolutely necessary) - b[0] = AREA2(point, v1, v2) * invArea; - - if ((b[0] < 0.0f) || (b[0] > 1.0f)) { - return false; - } - - b[1] = AREA2(v0, point, v2) * invArea; - if ((b[1] < 0.0f) || (b[1] > 1.0f)) { - return false; - } - - b[2] = 1.0f - b[0] - b[1]; - -# undef AREA2 - - return (b[2] >= 0.0f) && (b[2] <= 1.0f); -} - - -bool CollisionDetection::isPointInsideRectangle( - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& v3, - const Vector3& normal, - const Vector3& point) { - - return isPointInsideTriangle(v0, v1, v2, normal, point) || - isPointInsideTriangle(v2, v3, v0, normal, point); -} - - -Vector3 CollisionDetection::closestPointToRectanglePerimeter( - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& v3, - const Vector3& point) { - - Vector3 r0 = closestPointOnLineSegment(v0, v1, point); - Vector3 r1 = closestPointOnLineSegment(v1, v2, point); - Vector3 r2 = closestPointOnLineSegment(v2, v3, point); - Vector3 r3 = closestPointOnLineSegment(v3, v0, point); - - double d0 = (r0 - point).squaredMagnitude(); - double d1 = (r1 - point).squaredMagnitude(); - double d2 = (r2 - point).squaredMagnitude(); - double d3 = (r3 - point).squaredMagnitude(); - - if (d0 < d1) { - if (d0 < d2) { - if (d0 < d3) { - return r0; - } else { - return r3; - } - } else { - if (d2 < d3) { - return r2; - } else { - return r3; - } - } - } else { - if (d1 < d2) { - if (d1 < d3) { - return r1; - } else { - return r3; - } - } else { - if (d2 < d3) { - return r2; - } else { - return r3; - } - } - } -} - - -Vector3 CollisionDetection::closestPointToRectangle( - const Vector3& v0, - const Vector3& v1, - const Vector3& v2, - const Vector3& v3, - const Vector3& point) { - - Plane plane(v0, v1, v2); - - // Project the point into the plane - double a, b, c, d; - plane.getEquation(a, b, c, d); - - double distance = a*point.x + b*point.y + c*point.z + d; - Vector3 planePoint = point - distance * plane.normal(); - - if (isPointInsideRectangle(v0, v1, v2, v3, plane.normal(), planePoint)) { - return planePoint; - } else { - return closestPointToRectanglePerimeter(v0, v1, v2, v3, planePoint); - } -} - - -bool CollisionDetection::fixedSolidSphereIntersectsFixedSolidSphere( - const Sphere& sphere1, - const Sphere& sphere2) { - - return (sphere1.center - sphere2.center).squaredMagnitude() < square(sphere1.radius + sphere2.radius); -} - - -bool CollisionDetection::fixedSolidSphereIntersectsFixedSolidBox( - const Sphere& sphere, - const Box& box) { - - // If the center of the sphere is within the box, the whole - // sphere is within the box. - if (box.contains(sphere.center)) { - return true; - } - - float r2 = square(sphere.radius); - - // Find the closest point on the surface of the box to the sphere. If - // this point is within the sphere's radius, they intersect. - int f; - for (f = 0; f < 6; ++f) { - Vector3 v0, v1, v2, v3; - box.getFaceCorners(f, v0, v1, v2, v3); - if ((closestPointToRectangle(v0, v1, v2, v3, sphere.center) - sphere.center).squaredMagnitude() <= r2) { - return true; - } - } - - return false; -} - - -bool CollisionDetection::movingSpherePassesThroughFixedBox( - const Sphere& sphere, - const Vector3& velocity, - const Box& box, - double timeLimit) { - - // If they intersect originally, they definitely pass through each other. - if (fixedSolidSphereIntersectsFixedSolidBox(sphere, box)) { - return true; - } - - // See if the sphere hits the box during the time period. - Vector3 dummy1, dummy2; - - return (collisionTimeForMovingSphereFixedBox(sphere, velocity, box, dummy1, dummy2) < timeLimit); -} - - -bool CollisionDetection::movingSpherePassesThroughFixedSphere( - const Sphere& sphere, - const Vector3& velocity, - const Sphere& fixedSphere, - double timeLimit) { - - if (fixedSolidSphereIntersectsFixedSolidSphere(sphere, fixedSphere)) { - return true; - } - - // Extend the fixed sphere by the radius of the moving sphere - Sphere bigFixed(fixedSphere.center, fixedSphere.radius + sphere.radius); - Vector3 dummy1, dummy2; - - // If the sphere collides with the other sphere during the time limit, it passes through - return (collisionTimeForMovingPointFixedSphere(sphere.center, velocity, bigFixed, dummy1, dummy2) < timeLimit); -} - - - -bool CollisionDetection::fixedSolidSphereIntersectsFixedTriangle( - const Sphere& sphere, - const Triangle& triangle) { - - // How far is the sphere from the plane of the triangle - const Plane& plane = triangle.plane(); - - // Does the closest point to the sphere center lie within the triangle? - Vector3 v = plane.closestPoint(sphere.center); - - // Is the closest point to the plane within the sphere? - if ((v - sphere.center).squaredLength() <= square(sphere.radius)) { - // Is it also within the triangle? - float b[3]; - if (isPointInsideTriangle(triangle.vertex(0), triangle.vertex(1), triangle.vertex(2), triangle.normal(), - v, b, triangle.primaryAxis())){ - // The closest point is inside the triangle - return true; - } - } - - // ignored - int edgeIndex; - - v = closestPointOnTrianglePerimeter(triangle._vertex, triangle.edgeDirection, triangle.edgeMagnitude, sphere.center, edgeIndex); - - // Is the closest point within the sphere? - return ((v - sphere.center).squaredLength() <= square(sphere.radius)); -} - - -//////////////////////////////////////////////////////////////////////////////// -// AABB-triangle overlap test code based on Tomas Akenine-Möller's -// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt -// Ported 2008-12-28 - -#define X 0 -#define Y 1 -#define Z 2 - -#define FINDMINMAX(x0, x1, x2, min, max) \ - min = max = x0; \ - if(x1<min) min=x1;\ - if(x1>max) max=x1;\ - if(x2<min) min=x2;\ - if(x2>max) max=x2; - -static bool planeBoxOverlap(const Vector3& normal, const Vector3& vert, const Vector3& maxbox) { - Vector3 vmin, vmax; - float v; - - // for each axis - for(int a = 0; a < 3; ++a) { - v = vert[a]; - - if (normal[a] > 0.0f) { - vmin[a] = -maxbox[a] - v; - vmax[a] = maxbox[a] - v; - } else { - vmin[a] = maxbox[a] - v; - vmax[a] = -maxbox[a] - v; - } - } - - if (normal.dot(vmin) > 0.0f) { - return false; - } else if (normal.dot(vmax) >= 0.0f) { - return true; - } else { - return false; - } -} - -/*======================== X-tests ========================*/ - -#define AXISTEST_X01(a, b, fa, fb) \ - p0 = a*v0[Y] - b*v0[Z]; \ - p2 = a*v2[Y] - b*v2[Z]; \ - if(p0<p2) {min=p0; max=p2;} else {min=p2; max=p0;} \ - rad = fa * boxhalfsize[Y] + fb * boxhalfsize[Z]; \ - if(min>rad || max<-rad) return false; - - -#define AXISTEST_X2(a, b, fa, fb) \ - p0 = a*v0[Y] - b*v0[Z]; \ - p1 = a*v1[Y] - b*v1[Z]; \ - if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \ - rad = fa * boxhalfsize[Y] + fb * boxhalfsize[Z]; \ - if(min>rad || max<-rad) return false; - -/*======================== Y-tests ========================*/ - -#define AXISTEST_Y02(a, b, fa, fb) \ - p0 = -a*v0[X] + b*v0[Z]; \ - p2 = -a*v2[X] + b*v2[Z]; \ - if(p0<p2) {min=p0; max=p2;} else {min=p2; max=p0;} \ - rad = fa * boxhalfsize[X] + fb * boxhalfsize[Z]; \ - if(min>rad || max<-rad) return false; - -#define AXISTEST_Y1(a, b, fa, fb) \ - p0 = -a*v0[X] + b*v0[Z]; \ - p1 = -a*v1[X] + b*v1[Z]; \ - if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \ - rad = fa * boxhalfsize[X] + fb * boxhalfsize[Z]; \ - if(min>rad || max<-rad) return false; - -/*======================== Z-tests ========================*/ - -#define AXISTEST_Z12(a, b, fa, fb) \ - p1 = a*v1[X] - b*v1[Y]; \ - p2 = a*v2[X] - b*v2[Y]; \ - if(p2<p1) {min=p2; max=p1;} else {min=p1; max=p2;} \ - rad = fa * boxhalfsize[X] + fb * boxhalfsize[Y]; \ - if(min>rad || max<-rad) return false; - -#define AXISTEST_Z0(a, b, fa, fb) \ - p0 = a*v0[X] - b*v0[Y]; \ - p1 = a*v1[X] - b*v1[Y]; \ - if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \ - rad = fa * boxhalfsize[X] + fb * boxhalfsize[Y]; \ - if(min>rad || max<-rad) return false; - -bool CollisionDetection::fixedSolidBoxIntersectsFixedTriangle( - const AABox& box, const Triangle& tri) { - - // use separating axis theorem to test overlap between triangle and box - // need to test for overlap in these directions: - // 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle - // we do not even need to test these) - // 2) normal of the triangle - // 3) crossproduct(edge from tri, {x,y,z}-direction) - // this gives 3x3=9 more tests - - // This is the fastest branch (on Sun). - // Move the triangle to the object space of the box - // Triangle vertices in box object space - - const Vector3& boxcenter = box.center(); - const Vector3& boxhalfsize = box.extent() * 0.5f; - - const Vector3& v0 = tri.vertex(0) - boxcenter; - const Vector3& v1 = tri.vertex(1) - boxcenter; - const Vector3& v2 = tri.vertex(2) - boxcenter; - - // Compute triangle edges in object space - const Vector3& e0 = v1 - v0; - const Vector3& e1 = v2 - v1; - const Vector3& e2 = v0 - v2; - - // Bullet 3: - // test the 9 tests first (this was faster) - float min,max,p0,p1,p2,rad; - Vector3 fe; - - fe = abs(e0); - AXISTEST_X01(e0[Z], e0[Y], fe[Z], fe[Y]); - AXISTEST_Y02(e0[Z], e0[X], fe[Z], fe[X]); - AXISTEST_Z12(e0[Y], e0[X], fe[Y], fe[X]); - - fe = abs(e1); - AXISTEST_X01(e1[Z], e1[Y], fe[Z], fe[Y]); - AXISTEST_Y02(e1[Z], e1[X], fe[Z], fe[X]); - AXISTEST_Z0 (e1[Y], e1[X], fe[Y], fe[X]); - - fe = abs(e2); - AXISTEST_X2 (e2[Z], e2[Y], fe[Z], fe[Y]); - AXISTEST_Y1 (e2[Z], e2[X], fe[Z], fe[X]); - AXISTEST_Z12(e2[Y], e2[X], fe[Y], fe[X]); - - // Bullet 1: - // first test overlap in the {x,y,z}-directions - // find min, max of the triangle each direction, and test for overlap in - // that direction -- this is equivalent to testing a minimal AABB around - // the triangle against the AABB - - // test in X-direction - FINDMINMAX(v0[X],v1[X],v2[X],min,max); - if (min > boxhalfsize[X] || max < -boxhalfsize[X]) { - return false; - } - - // test in Y-direction - FINDMINMAX(v0[Y],v1[Y],v2[Y],min,max); - if (min > boxhalfsize[Y] || max < -boxhalfsize[Y]) { - return false; - } - - // test in Z-direction - FINDMINMAX(v0[Z],v1[Z],v2[Z],min,max); - if (min > boxhalfsize[Z] || max < -boxhalfsize[Z]) { - return false; - } - - // Bullet 2: - // test if the box intersects the plane of the triangle - // compute plane equation of triangle: normal*x+d=0 - - if (! planeBoxOverlap(tri.normal(), v0, boxhalfsize)) { - return false; - } - - // box and triangle overlap - return true; -} -#undef X -#undef Y -#undef Z - -//////////////////////////////////////////////////////////////////////////////// - - -} // namespace - -#ifdef _MSC_VER -// Turn off fast floating-point optimizations -#pragma float_control( pop ) -#pragma warning (pop) -#endif |