aboutsummaryrefslogtreecommitdiff
path: root/dep/g3dlite/source/CollisionDetection.cpp
diff options
context:
space:
mode:
authorclick <none@none>2010-08-27 01:52:05 +0200
committerclick <none@none>2010-08-27 01:52:05 +0200
commit7686d6ee57038e848f75130563fc78c1adebc1a2 (patch)
tree258a7a6db03e3479a0219ace8cfc3ec3ba53a1be /dep/g3dlite/source/CollisionDetection.cpp
parent1d3deae555f0ec523d77875bd5a307ab9bd19742 (diff)
Core/Dependency: Upgrade G3d-library to v8.0-release (patched version!)
Note: Due to issues with G3D (normally requiring X11 and the ZIP-library), the sourcetree version contains a modified version. The applied patch is commited to the repository for future reference. --HG-- branch : trunk
Diffstat (limited to 'dep/g3dlite/source/CollisionDetection.cpp')
-rw-r--r--dep/g3dlite/source/CollisionDetection.cpp2455
1 files changed, 2455 insertions, 0 deletions
diff --git a/dep/g3dlite/source/CollisionDetection.cpp b/dep/g3dlite/source/CollisionDetection.cpp
new file mode 100644
index 00000000000..77eef0a5500
--- /dev/null
+++ b/dep/g3dlite/source/CollisionDetection.cpp
@@ -0,0 +1,2455 @@
+/**
+ @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