| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- // ----------------------------------------------------------------
- // From Game Programming in C++ by Sanjay Madhav
- // Copyright (C) 2017 Sanjay Madhav. All rights reserved.
- //
- // Released under the BSD License
- // See LICENSE in root directory for full details.
- // ----------------------------------------------------------------
- // MinDistSq between two line segments:
- // Copyright 2001 softSurfer, 2012 Dan Sunday
- // This code may be freely used, distributed and modified for any purpose
- // providing that this copyright notice is included with it.
- // SoftSurfer makes no warranty for this code, and cannot be held
- // liable for any real or imagined damage resulting from its use.
- // Users of this code must verify correctness for their application.
- // ----------------------------------------------------------------
- #include "Collision.h"
- #include <algorithm>
- #include <array>
- LineSegment::LineSegment(const Vector3& start, const Vector3& end)
- :mStart(start)
- ,mEnd(end)
- {
- }
- Vector3 LineSegment::PointOnSegment(float t) const
- {
- return mStart + (mEnd - mStart) * t;
- }
- float LineSegment::MinDistSq(const Vector3& point) const
- {
- // Construct vectors
- Vector3 ab = mEnd - mStart;
- Vector3 ba = -1.0f * ab;
- Vector3 ac = point - mStart;
- Vector3 bc = point - mEnd;
- // Case 1: C projects prior to A
- if (Vector3::Dot(ab, ac) < 0.0f)
- {
- return ac.LengthSq();
- }
- // Case 2: C projects after B
- else if (Vector3::Dot(ba, bc) < 0.0f)
- {
- return bc.LengthSq();
- }
- // Case 3: C projects onto line
- else
- {
- // Compute p
- float scalar = Vector3::Dot(ac, ab)
- / Vector3::Dot(ab, ab);
- Vector3 p = scalar * ab;
- // Compute length squared of ac - p
- return (ac - p).LengthSq();
- }
- }
- float LineSegment::MinDistSq(const LineSegment & s1, const LineSegment & s2)
- {
- Vector3 u = s1.mEnd - s1.mStart;
- Vector3 v = s2.mEnd - s2.mStart;
- Vector3 w = s1.mStart - s2.mStart;
- float a = Vector3::Dot(u, u); // always >= 0
- float b = Vector3::Dot(u, v);
- float c = Vector3::Dot(v, v); // always >= 0
- float d = Vector3::Dot(u, w);
- float e = Vector3::Dot(v, w);
- float D = a*c - b*b; // always >= 0
- float sc, sN, sD = D; // sc = sN / sD, default sD = D >= 0
- float tc, tN, tD = D; // tc = tN / tD, default tD = D >= 0
- // compute the line parameters of the two closest points
- if (Math::NearZero(D)) { // the lines are almost parallel
- sN = 0.0; // force using point P0 on segment S1
- sD = 1.0; // to prevent possible division by 0.0 later
- tN = e;
- tD = c;
- }
- else { // get the closest points on the infinite lines
- sN = (b*e - c*d);
- tN = (a*e - b*d);
- if (sN < 0.0) { // sc < 0 => the s=0 edge is visible
- sN = 0.0;
- tN = e;
- tD = c;
- }
- else if (sN > sD) { // sc > 1 => the s=1 edge is visible
- sN = sD;
- tN = e + b;
- tD = c;
- }
- }
- if (tN < 0.0) { // tc < 0 => the t=0 edge is visible
- tN = 0.0;
- // recompute sc for this edge
- if (-d < 0.0)
- sN = 0.0;
- else if (-d > a)
- sN = sD;
- else {
- sN = -d;
- sD = a;
- }
- }
- else if (tN > tD) { // tc > 1 => the t=1 edge is visible
- tN = tD;
- // recompute sc for this edge
- if ((-d + b) < 0.0)
- sN = 0;
- else if ((-d + b) > a)
- sN = sD;
- else {
- sN = (-d + b);
- sD = a;
- }
- }
- // finally do the division to get sc and tc
- sc = (Math::NearZero(sN) ? 0.0f : sN / sD);
- tc = (Math::NearZero(tN) ? 0.0f : tN / tD);
- // get the difference of the two closest points
- Vector3 dP = w + (sc * u) - (tc * v); // = S1(sc) - S2(tc)
- return dP.LengthSq(); // return the closest distance squared
- }
- Plane::Plane(const Vector3& normal, float d)
- :mNormal(normal)
- ,mD(d)
- {
- }
- Plane::Plane(const Vector3& a, const Vector3& b, const Vector3& c)
- {
- // Compute vectors from a to b and a to c
- Vector3 ab = b - a;
- Vector3 ac = c - a;
- // Cross product and normalize to get normal
- mNormal = Vector3::Cross(ab, ac);
- mNormal.Normalize();
- // d = -P dot n
- mD = -Vector3::Dot(a, mNormal);
- }
- float Plane::SignedDist(const Vector3& point) const
- {
- return Vector3::Dot(point, mNormal) - mD;
- }
- Sphere::Sphere(const Vector3& center, float radius)
- :mCenter(center)
- , mRadius(radius)
- {
- }
- bool Sphere::Contains(const Vector3& point) const
- {
- // Get distance squared between center and point
- float distSq = (mCenter - point).LengthSq();
- return distSq <= (mRadius * mRadius);
- }
- AABB::AABB(const Vector3& min, const Vector3& max)
- : mMin(min)
- , mMax(max)
- {
- }
- void AABB::UpdateMinMax(const Vector3& point)
- {
- // Update each component separately
- mMin.x = Math::Min(mMin.x, point.x);
- mMin.y = Math::Min(mMin.y, point.y);
- mMin.z = Math::Min(mMin.z, point.z);
- mMax.x = Math::Max(mMax.x, point.x);
- mMax.y = Math::Max(mMax.y, point.y);
- mMax.z = Math::Max(mMax.z, point.z);
- }
- void AABB::Rotate(const Quaternion& q)
- {
- // Construct the 8 points for the corners of the box
- std::array<Vector3, 8> points;
- // Min point is always a corner
- points[0] = mMin;
- // Permutations with 2 min and 1 max
- points[1] = Vector3(mMax.x, mMin.y, mMin.z);
- points[2] = Vector3(mMin.x, mMax.y, mMin.z);
- points[3] = Vector3(mMin.x, mMin.y, mMax.z);
- // Permutations with 2 max and 1 min
- points[4] = Vector3(mMin.x, mMax.y, mMax.z);
- points[5] = Vector3(mMax.x, mMin.y, mMax.z);
- points[6] = Vector3(mMax.x, mMax.y, mMin.z);
- // Max point corner
- points[7] = Vector3(mMax);
- // Rotate first point
- Vector3 p = Vector3::Transform(points[0], q);
- // Reset min/max to first point rotated
- mMin = p;
- mMax = p;
- // Update min/max based on remaining points, rotated
- for (size_t i = 1; i < points.size(); i++)
- {
- p = Vector3::Transform(points[i], q);
- UpdateMinMax(p);
- }
- }
- bool AABB::Contains(const Vector3& point) const
- {
- bool outside = point.x < mMin.x ||
- point.y < mMin.y ||
- point.z < mMin.z ||
- point.x > mMax.x ||
- point.y > mMax.y ||
- point.z > mMax.z;
- // If none of these are true, the point is inside the box
- return !outside;
- }
- float AABB::MinDistSq(const Vector3& point) const
- {
- // Compute differences for each axis
- float dx = Math::Max(mMin.x - point.x, 0.0f);
- dx = Math::Max(dx, point.x - mMax.x);
- float dy = Math::Max(mMin.y - point.y, 0.0f);
- dy = Math::Max(dy, point.y - mMax.y);
- float dz = Math::Max(mMin.z - point.z, 0.0f);
- dz = Math::Max(dy, point.z - mMax.z);
- // Distance squared formula
- return dx * dx + dy * dy + dz * dz;
- }
- Capsule::Capsule(const Vector3& start, const Vector3& end, float radius)
- :mSegment(start, end)
- , mRadius(radius)
- {
- }
- Vector3 Capsule::PointOnSegment(float t) const
- {
- return mSegment.PointOnSegment(t);
- }
- bool Capsule::Contains(const Vector3& point) const
- {
- // Get minimal dist. sq. between point and line segment
- float distSq = mSegment.MinDistSq(point);
- return distSq <= (mRadius * mRadius);
- }
- bool ConvexPolygon::Contains(const Vector2& point) const
- {
- float sum = 0.0f;
- Vector2 a, b;
- for (size_t i = 0; i < mVertices.size() - 1; i++)
- {
- // From point to first vertex
- a = mVertices[i] - point;
- a.Normalize();
- // From point to second vertex
- b = mVertices[i + 1] - point;
- b.Normalize();
- // Add angle to sum
- sum += Math::Acos(Vector2::Dot(a, b));
- }
- // Have to add angle for last vertex and first vertex
- a = mVertices.back() - point;
- a.Normalize();
- b = mVertices.front() - point;
- b.Normalize();
- sum += Math::Acos(Vector2::Dot(a, b));
- // Return true if approximately 2pi
- return Math::NearZero(sum - Math::TwoPi);
- }
- bool Intersect(const Sphere& a, const Sphere& b)
- {
- float distSq = (a.mCenter - b.mCenter).LengthSq();
- float sumRadii = a.mRadius + b.mRadius;
- return distSq <= (sumRadii * sumRadii);
- }
- bool Intersect(const AABB& a, const AABB& b)
- {
- bool no = a.mMax.x < b.mMin.x ||
- a.mMax.y < b.mMin.y ||
- a.mMax.z < b.mMin.z ||
- b.mMax.x < a.mMin.x ||
- b.mMax.y < a.mMin.y ||
- b.mMax.z < a.mMin.z;
- // If none of these are true, they must intersect
- return !no;
- }
- bool Intersect(const Capsule& a, const Capsule& b)
- {
- float distSq = LineSegment::MinDistSq(a.mSegment,
- b.mSegment);
- float sumRadii = a.mRadius + b.mRadius;
- return distSq <= (sumRadii * sumRadii);
- }
- bool Intersect(const Sphere& s, const AABB& box)
- {
- float distSq = box.MinDistSq(s.mCenter);
- return distSq <= (s.mRadius * s.mRadius);
- }
- bool Intersect(const LineSegment& l, const Sphere& s, float& outT)
- {
- // Compute X, Y, a, b, c as per equations
- Vector3 X = l.mStart - s.mCenter;
- Vector3 Y = l.mEnd - l.mStart;
- float a = Vector3::Dot(Y, Y);
- float b = 2.0f * Vector3::Dot(X, Y);
- float c = Vector3::Dot(X, X) - s.mRadius * s.mRadius;
- // Compute discriminant
- float disc = b * b - 4.0f * a * c;
- if (disc < 0.0f)
- {
- return false;
- }
- else
- {
- disc = Math::Sqrt(disc);
- // Compute min and max solutions of t
- float tMin = (-b - disc) / (2.0f * a);
- float tMax = (-b + disc) / (2.0f * a);
- // Check whether either t is within bounds of segment
- if (tMin >= 0.0f && tMin <= 1.0f)
- {
- outT = tMin;
- return true;
- }
- else if (tMax >= 0.0f && tMax <= 1.0f)
- {
- outT = tMax;
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- bool Intersect(const LineSegment& l, const Plane& p, float& outT)
- {
- // First test if there's a solution for t
- float denom = Vector3::Dot(l.mEnd - l.mStart,
- p.mNormal);
- if (Math::NearZero(denom))
- {
- // The only way they intersect is if start
- // is a point on the plane (P dot N) == d
- if (Math::NearZero(Vector3::Dot(l.mStart, p.mNormal)
- - p.mD))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- else
- {
- float numer = -Vector3::Dot(l.mStart, p.mNormal) -
- p.mD;
- outT = numer / denom;
- // Validate t is within bounds of the line segment
- if (outT >= 0.0f && outT <= 1.0f)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- bool TestSidePlane(float start, float end, float negd, const Vector3& norm,
- std::vector<std::pair<float, Vector3>>& out)
- {
- float denom = end - start;
- if (Math::NearZero(denom))
- {
- return false;
- }
- else
- {
- float numer = -start + negd;
- float t = numer / denom;
- // Test that t is within bounds
- if (t >= 0.0f && t <= 1.0f)
- {
- out.emplace_back(t, norm);
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- bool Intersect(const LineSegment& l, const AABB& b, float& outT,
- Vector3& outNorm)
- {
- // Vector to save all possible t values, and normals for those sides
- std::vector<std::pair<float, Vector3>> tValues;
- // Test the x planes
- TestSidePlane(l.mStart.x, l.mEnd.x, b.mMin.x, Vector3::NegUnitX,
- tValues);
- TestSidePlane(l.mStart.x, l.mEnd.x, b.mMax.x, Vector3::UnitX,
- tValues);
- // Test the y planes
- TestSidePlane(l.mStart.y, l.mEnd.y, b.mMin.y, Vector3::NegUnitY,
- tValues);
- TestSidePlane(l.mStart.y, l.mEnd.y, b.mMax.y, Vector3::UnitY,
- tValues);
- // Test the z planes
- TestSidePlane(l.mStart.z, l.mEnd.z, b.mMin.z, Vector3::NegUnitZ,
- tValues);
- TestSidePlane(l.mStart.z, l.mEnd.z, b.mMax.z, Vector3::UnitZ,
- tValues);
-
- // Sort the t values in ascending order
- std::sort(tValues.begin(), tValues.end(), [](
- const std::pair<float, Vector3>& a,
- const std::pair<float, Vector3>& b) {
- return a.first < b.first;
- });
- // Test if the box contains any of these points of intersection
- Vector3 point;
- for (auto& t : tValues)
- {
- point = l.PointOnSegment(t.first);
- if (b.Contains(point))
- {
- outT = t.first;
- outNorm = t.second;
- return true;
- }
- }
- //None of the intersections are within bounds of box
- return false;
- }
- bool SweptSphere(const Sphere& P0, const Sphere& P1,
- const Sphere& Q0, const Sphere& Q1, float& outT)
- {
- // Compute X, Y, a, b, and c
- Vector3 X = P0.mCenter - Q0.mCenter;
- Vector3 Y = P1.mCenter - P0.mCenter -
- (Q1.mCenter - Q0.mCenter);
- float a = Vector3::Dot(Y, Y);
- float b = 2.0f * Vector3::Dot(X, Y);
- float sumRadii = P0.mRadius + Q0.mRadius;
- float c = Vector3::Dot(X, X) - sumRadii * sumRadii;
- // Solve discriminant
- float disc = b * b - 4.0f * a * c;
- if (disc < 0.0f)
- {
- return false;
- }
- else
- {
- disc = Math::Sqrt(disc);
- // We only care about the smaller solution
- outT = (-b - disc) / (2.0f * a);
- if (outT >= 0.0f && outT <= 0.0f)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- }
|