#include "Runtime/Collision/CollisionUtil.hpp" #include #include #include #include "Runtime/Collision/CCollisionInfo.hpp" #include "Runtime/Collision/CCollisionInfoList.hpp" #include namespace metaforce::CollisionUtil { bool LineIntersectsOBBox(const zeus::COBBox& obb, const zeus::CMRay& ray, float& d) { zeus::CVector3f norm; return RayAABoxIntersection(ray.getInvUnscaledTransformRay(obb.transform), {-obb.extents, obb.extents}, norm, d); } u32 RayAABoxIntersection(const zeus::CMRay& ray, const zeus::CAABox& aabb, float& tMin, float& tMax) { tMin = -999999.f; tMax = 999999.f; for (size_t i = 0; i < 3; ++i) { if (std::fabs(ray.dir[i]) < 0.00001f) { if (ray.start[i] < aabb.min[i] || ray.start[i] > aabb.max[i]) { return 0; } } else { if (ray.dir[i] < 0.f) { const float startToMax = aabb.max[i] - ray.start[i]; const float startToMin = aabb.min[i] - ray.start[i]; const float dirRecip = 1.f / ray.dir[i]; if (startToMax < tMin * ray.dir[i]) { tMin = startToMax * dirRecip; } if (startToMin > tMax * ray.dir[i]) { tMax = startToMin * dirRecip; } } else { const float startToMin = aabb.min[i] - ray.start[i]; const float startToMax = aabb.max[i] - ray.start[i]; const float dirRecip = 1.f / ray.dir[i]; if (startToMin > tMin * ray.dir[i]) { tMin = startToMin * dirRecip; } if (startToMax < tMax * ray.dir[i]) { tMax = startToMax * dirRecip; } } } } return tMin <= tMax ? 2 : 0; } u32 RayAABoxIntersection(const zeus::CMRay& ray, const zeus::CAABox& aabb, zeus::CVector3f& norm, float& d) { std::array sign{2, 2, 2}; bool bad = true; zeus::CVector3f rayStart = ray.start; zeus::CVector3f rayDelta = ray.delta; zeus::CVector3f aabbMin = aabb.min; zeus::CVector3f aabbMax = aabb.max; zeus::CVector3f vec0 = {-1.f, -1.f, -1.f}; zeus::CVector3f vec1; if (rayDelta.x() != 0.f && rayDelta.y() != 0.f && rayDelta.z() != 0.f) { for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; vec0[i] = (aabbMin[i] - rayStart[i]) / rayDelta[i]; } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; vec0[i] = (aabbMax[i] - rayStart[i]) / rayDelta[i]; } } if (bad) { d = 0.f; return 1; } } else { zeus::CVector3f end; for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; end[i] = float(aabbMin[i]); } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; end[i] = float(aabbMax[i]); } } if (bad) { d = 0.f; return 1; } for (size_t i = 0; i < sign.size(); ++i) { if (sign[i] != 2 && rayDelta[i] != 0.f) { vec0[i] = (end[i] - rayStart[i]) / rayDelta[i]; } } } float maxComp = vec0.x(); size_t maxCompIdx = 0; if (maxComp < vec0.y()) { maxComp = vec0.y(); maxCompIdx = 1; } if (maxComp < vec0.z()) { maxComp = vec0.z(); maxCompIdx = 2; } if (maxComp < 0.f || maxComp > 1.f) return 0; for (size_t i = 0; i < 3; ++i) { if (maxCompIdx != i) { vec1[i] = maxComp * rayDelta[i] + rayStart[i]; if (vec1[i] > aabbMax[i]) { return 0; } } } d = maxComp; norm = zeus::skZero3f; norm[maxCompIdx] = (sign[maxCompIdx] == 1) ? -1.f : 1.f; return 2; } u32 RayAABoxIntersection_Double(const zeus::CMRay& ray, const zeus::CAABox& aabb, zeus::CVector3f& norm, double& d) { std::array sign{2, 2, 2}; bool bad = true; zeus::CVector3d rayStart = ray.start; zeus::CVector3d rayDelta = ray.delta; zeus::CVector3d aabbMin = aabb.min; zeus::CVector3d aabbMax = aabb.max; zeus::CVector3d vec0 = {-1.0, -1.0, -1.0}; zeus::CVector3d vec1; if (rayDelta.x() != 0.0 && rayDelta.y() != 0.0 && rayDelta.z() != 0.0) { for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; vec0[i] = (aabbMin[i] - rayStart[i]) / rayDelta[i]; } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; vec0[i] = (aabbMax[i] - rayStart[i]) / rayDelta[i]; } } if (bad) { d = 0.0; return 1; } } else { zeus::CVector3d end; for (size_t i = 0; i < sign.size(); ++i) { if (rayStart[i] < aabbMin[i]) { sign[i] = 1; bad = false; end[i] = double(aabbMin[i]); } else if (rayStart[i] > aabbMax[i]) { sign[i] = 0; bad = false; end[i] = double(aabbMax[i]); } } if (bad) { d = 0.0; return 1; } for (size_t i = 0; i < sign.size(); ++i) { if (sign[i] != 2 && rayDelta[i] != 0.0) { vec0[i] = (end[i] - rayStart[i]) / rayDelta[i]; } } } double maxComp = vec0.x(); size_t maxCompIdx = 0; if (maxComp < vec0.y()) { maxComp = vec0.y(); maxCompIdx = 1; } if (maxComp < vec0.z()) { maxComp = vec0.z(); maxCompIdx = 2; } if (maxComp < 0.0 || maxComp > 1.0) return 0; for (size_t i = 0; i < 3; ++i) { if (maxCompIdx != i) { vec1[i] = maxComp * rayDelta[i] + rayStart[i]; if (vec1[i] > aabbMax[i]) { return 0; } } } d = maxComp; norm = zeus::skZero3f; norm[maxCompIdx] = (sign[maxCompIdx] == 1) ? -1.0 : 1.0; return 2; } bool RaySphereIntersection_Double(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir, double& T) { const zeus::CVector3d sPosD = sphere.position; const zeus::CVector3d posD = pos; const zeus::CVector3d sphereToPos = posD - sPosD; const double f30 = sphereToPos.dot(zeus::CVector3d(dir)) * 2.0; const double f1 = f30 * f30 - 4.0 * (sphereToPos.magSquared() - sphere.radius * sphere.radius); if (f1 >= 0.0) { const double intersectT = 0.5 * (-f30 - std::sqrt(f1)); if (T == 0 || intersectT < T) { T = intersectT; return true; } } return false; } bool RaySphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& pos, const zeus::CVector3f& dir, float mag, float& T, zeus::CVector3f& point) { const zeus::CVector3f rayToSphere = sphere.position - pos; const float magSq = rayToSphere.magSquared(); const float dirDot = rayToSphere.dot(dir); const float radSq = sphere.radius * sphere.radius; if (dirDot < 0.f && magSq > radSq) { return false; } const float intersectSq = radSq - (magSq - dirDot * dirDot); if (intersectSq < 0.f) { return false; } T = magSq > radSq ? dirDot - std::sqrt(intersectSq) : dirDot + std::sqrt(intersectSq); if (T < mag || mag == 0.f) { point = pos + T * dir; return true; } return false; } bool RayTriangleIntersection_Double(const zeus::CVector3f& point, const zeus::CVector3f& dir, const std::array& verts, double& d) { const zeus::CVector3d v0tov1 = verts[1] - verts[0]; const zeus::CVector3d v0tov2 = verts[2] - verts[0]; const zeus::CVector3d cross0 = zeus::CVector3d(dir).cross(v0tov2); const double dot0 = v0tov1.dot(cross0); if (dot0 < DBL_EPSILON) { return false; } const zeus::CVector3d v0toPoint = point - verts[0]; const double dot1 = v0toPoint.dot(cross0); if (dot1 < 0.0 || dot1 > dot0) { return false; } const zeus::CVector3d cross1 = v0toPoint.cross(v0tov1); const double dot2 = cross1.dot(dir); if (dot2 < 0.0 || dot1 + dot2 > dot0) { return false; } const double final = 1.0 / dot0 * cross1.dot(v0tov2); if (final < 0.0 || final >= d) { return false; } d = final; return true; } bool RayTriangleIntersection(const zeus::CVector3f& point, const zeus::CVector3f& dir, const std::array& verts, float& d) { const zeus::CVector3f v0tov1 = verts[1] - verts[0]; const zeus::CVector3f v0tov2 = verts[2] - verts[0]; const zeus::CVector3f cross0 = dir.cross(v0tov2); const float dot0 = v0tov1.dot(cross0); if (dot0 < DBL_EPSILON) { return false; } const zeus::CVector3f v0toPoint = point - verts[0]; const float dot1 = v0toPoint.dot(cross0); if (dot1 < 0.f || dot1 > dot0) { return false; } const zeus::CVector3f cross1 = v0toPoint.cross(v0tov1); const float dot2 = cross1.dot(dir); if (dot2 < 0.f || dot1 + dot2 > dot0) { return false; } const float final = 1.f / dot0 * cross1.dot(v0tov2); if (final < 0.f || final >= d) { return false; } d = final; return true; } void FilterOutBackfaces(const zeus::CVector3f& vec, const CCollisionInfoList& in, CCollisionInfoList& out) { if (vec.canBeNormalized()) { const zeus::CVector3f norm = vec.normalized(); for (const CCollisionInfo& info : in) { if (info.GetNormalLeft().dot(norm) < 0.001f) { out.Add(info, false); } } } else { out = in; } } void FilterByClosestNormal(const zeus::CVector3f& norm, const CCollisionInfoList& in, CCollisionInfoList& out) { float maxDot = -1.1f; int idx = -1; int i = 0; for (const CCollisionInfo& info : in) { const float dot = info.GetNormalLeft().dot(norm); if (dot > maxDot) { maxDot = dot; idx = i; } ++i; } if (idx != -1) { out.Add(in.GetItem(idx), false); } } constexpr std::array AABBNormalTable{{ {-1.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, -1.f}, {0.f, 0.f, 1.f}, }}; bool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const CMaterialList& list0, const zeus::CAABox& aabb1, const CMaterialList& list1, CCollisionInfoList& infoList) { const zeus::CVector3f maxOfMin(std::max(aabb0.min.x(), aabb1.min.x()), std::max(aabb0.min.y(), aabb1.min.y()), std::max(aabb0.min.z(), aabb1.min.z())); const zeus::CVector3f minOfMax(std::min(aabb0.max.x(), aabb1.max.x()), std::min(aabb0.max.y(), aabb1.max.y()), std::min(aabb0.max.z(), aabb1.max.z())); if (maxOfMin.x() >= minOfMax.x() || maxOfMin.y() >= minOfMax.y() || maxOfMin.z() >= minOfMax.z()) { return false; } const zeus::CAABox boolAABB(maxOfMin, minOfMax); const std::array ineqFlags{ (aabb0.min.x() <= aabb1.min.x() ? 1 << 0 : 0) | (aabb0.min.x() <= aabb1.max.x() ? 1 << 1 : 0) | (aabb0.max.x() <= aabb1.min.x() ? 1 << 2 : 0) | (aabb0.max.x() <= aabb1.max.x() ? 1 << 3 : 0), (aabb0.min.y() <= aabb1.min.y() ? 1 << 0 : 0) | (aabb0.min.y() <= aabb1.max.y() ? 1 << 1 : 0) | (aabb0.max.y() <= aabb1.min.y() ? 1 << 2 : 0) | (aabb0.max.y() <= aabb1.max.y() ? 1 << 3 : 0), (aabb0.min.z() <= aabb1.min.z() ? 1 << 0 : 0) | (aabb0.min.z() <= aabb1.max.z() ? 1 << 1 : 0) | (aabb0.max.z() <= aabb1.min.z() ? 1 << 2 : 0) | (aabb0.max.z() <= aabb1.max.z() ? 1 << 3 : 0), }; for (size_t i = 0; i < ineqFlags.size(); ++i) { switch (ineqFlags[i]) { case 0x2: // aabb0.min <= aabb1.max { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[i * 2 + 1], -AABBNormalTable[i * 2 + 1]); infoList.Add(info, false); break; } case 0xB: // aabb0.min <= aabb1.min && aabb0.max <= aabb1.min && aabb0.max <= aabb1.max { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[i * 2], -AABBNormalTable[i * 2]); infoList.Add(info, false); break; } default: break; } } if (infoList.GetCount() != 0) { return true; } { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[4], -AABBNormalTable[4]); infoList.Add(info, false); } { const CCollisionInfo info(boolAABB, list0, list1, AABBNormalTable[5], -AABBNormalTable[5]); infoList.Add(info, false); } return true; } bool AABoxAABoxIntersection(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1) { return aabb0.intersects(aabb1); } /* http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt */ /********************************************************/ /* AABB-triangle overlap test code */ /* by Tomas Akenine-Möller */ /* Function: int triBoxOverlap(float boxcenter[3], */ /* float boxhalfsize[3],float triverts[3][3]); */ /* History: */ /* 2001-03-05: released the code in its first version */ /* 2001-06-18: changed the order of the tests, faster */ /* */ /* Acknowledgement: Many thanks to Pierre Terdiman for */ /* suggestions and discussions on how to optimize code. */ /* Thanks to David Hunt for finding a ">="-bug! */ /********************************************************/ static bool planeBoxOverlap(const zeus::CVector3f& normal, float d, const zeus::CVector3f& maxbox) { zeus::CVector3f vmin; zeus::CVector3f vmax; for (int q = 0; q <= 2; q++) { if (normal[q] > 0.0f) { vmin[q] = -maxbox[q]; vmax[q] = maxbox[q]; } else { vmin[q] = maxbox[q]; vmax[q] = -maxbox[q]; } } if (normal.dot(vmin) + d > 0.0f) { return false; } if (normal.dot(vmax) + d >= 0.0f) { return true; } return false; } /*======================== X-tests ========================*/ #define AXISTEST_X01(a, b, fa, fb) \ do { \ 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; \ } while (false) #define AXISTEST_X2(a, b, fa, fb) \ do { \ 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; \ } while (false) /*======================== Y-tests ========================*/ #define AXISTEST_Y02(a, b, fa, fb) \ do { \ 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; \ } while (false) #define AXISTEST_Y1(a, b, fa, fb) \ do { \ 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; \ } while (false) /*======================== Z-tests ========================*/ #define AXISTEST_Z12(a, b, fa, fb) \ do { \ 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; \ } while (false) #define AXISTEST_Z0(a, b, fa, fb) \ do { \ 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; \ } while (false) bool TriBoxOverlap(const zeus::CVector3f& boxcenter, const zeus::CVector3f& boxhalfsize, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2) { /* 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}-directin) */ /* this gives 3x3=9 more tests */ float min, max, d, p0, p1, p2, rad, fex, fey, fez; /* This is the fastest branch on Sun */ /* move everything so that the boxcenter is in (0,0,0) */ const zeus::CVector3f v0 = trivert0 - boxcenter; const zeus::CVector3f v1 = trivert1 - boxcenter; const zeus::CVector3f v2 = trivert2 - boxcenter; /* compute triangle edges */ const zeus::CVector3f e0 = v1 - v0; // Tri edge 0 const zeus::CVector3f e1 = v2 - v1; // Tri edge 1 const zeus::CVector3f e2 = v0 - v2; // Tri edge 2 /* Bullet 3: */ /* test the 9 tests first (this was faster) */ fex = std::fabs(e0.x()); fey = std::fabs(e0.y()); fez = std::fabs(e0.z()); AXISTEST_X01(e0.z(), e0.y(), fez, fey); AXISTEST_Y02(e0.z(), e0.x(), fez, fex); AXISTEST_Z12(e0.y(), e0.x(), fey, fex); fex = std::fabs(e1.x()); fey = std::fabs(e1.y()); fez = std::fabs(e1.z()); AXISTEST_X01(e1.z(), e1.y(), fez, fey); AXISTEST_Y02(e1.z(), e1.x(), fez, fex); AXISTEST_Z0(e1.y(), e1.x(), fey, fex); fex = std::fabs(e2.x()); fey = std::fabs(e2.y()); fez = std::fabs(e2.z()); AXISTEST_X2(e2.z(), e2.y(), fez, fey); AXISTEST_Y1(e2.z(), e2.x(), fez, fex); AXISTEST_Z12(e2.y(), e2.x(), fey, fex); /* 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 */ std::tie(min, max) = std::minmax({v0.x(), v1.x(), v2.x()}); if (min > boxhalfsize.x() || max < -boxhalfsize.x()) { return false; } /* test in Y-direction */ std::tie(min, max) = std::minmax({v0.y(), v1.y(), v2.y()}); if (min > boxhalfsize.y() || max < -boxhalfsize.y()) { return false; } /* test in Z-direction */ std::tie(min, max) = std::minmax({v0.z(), v1.z(), v2.z()}); 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 */ const zeus::CVector3f normal = e0.cross(e1); d = -normal.dot(v0); /* plane eq: normal.x+d=0 */ if (!planeBoxOverlap(normal, d, boxhalfsize)) { return false; } return true; /* box and triangle overlaps */ } double TriPointSqrDist(const zeus::CVector3f& point, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, float* baryX, float* baryY) { const zeus::CVector3d A = trivert0 - point; const zeus::CVector3d B = trivert1 - trivert0; const zeus::CVector3d C = trivert2 - trivert0; const double bMag = B.magSquared(); const double cMag = C.magSquared(); const double bDotC = B.dot(C); const double aDotB = A.dot(B); const double aDotC = A.dot(C); double ret = A.magSquared(); const double rej = std::fabs(bMag * cMag - bDotC * bDotC); double retB = bDotC * aDotC - cMag * aDotB; double retA = bDotC * aDotB - bMag * aDotC; if (retB + retA <= rej) { if (retB < 0.0) { if (retA < 0.0) { if (aDotB < 0.0) { retA = 0.0; if (-aDotB >= bMag) { retB = 1.0; ret += 2.0 * aDotB + bMag; } else { retB = -aDotB / bMag; ret += aDotB * retB; } } else { retB = 0.0; if (aDotC >= 0.0) { retA = 0.0; } else if (-aDotC >= cMag) { retA = 1.0; ret += 2.0 * aDotC + cMag; } else { retA = -aDotC / cMag; ret += aDotC * retA; } } } else { retB = 0.0; if (aDotC >= 0.0) { retA = 0.0; } else if (-aDotC >= cMag) { retA = 1.0; ret += 2.0 * aDotC + cMag; } else { retA = -aDotC / cMag; ret += aDotC * retA; } } } else if (retA < 0.0) { retA = 0.0; if (aDotB >= 0.0) { retB = 0.0; } else if (-aDotB >= bMag) { retB = 1.0; ret += 2.0 * aDotB + bMag; } else { retB = -aDotB / bMag; ret += aDotB * retB; } } else { const float f3 = 1.0 / rej; retA *= f3; retB *= f3; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } else if (retB < 0.0) { retB = bDotC + aDotB; retA = cMag + aDotC; if (retA > retB) { retA -= retB; retB = bMag - 2.0 * bDotC; retB += cMag; if (retA >= retB) { retB = 1.0; retA = 0.0; ret += 2.0 * aDotB + bMag; } else { retB = retA / retB; retA = 1.0 - retB; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } else { retB = 0.0; if (retA <= 0.0) { retA = 1.0; ret += 2.0 * aDotC + cMag; } else if (aDotC >= 0.0) { retA = 0.0; } else { retA = -aDotC / cMag; ret += aDotC * retA; } } } else { if (retA < 0.0) { retB = bDotC + aDotC; retA = bMag + aDotB; if (retA > retB) { retA -= retB; retB = bMag - 2.0 * bDotC; retB += cMag; if (retA >= retB) { retA = 1.0; retB = 0.0; ret += 2.0 * aDotC + cMag; } else { retA /= retB; retB = 1.0 - retA; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } else { retA = 0.0; if (retA <= 0.0) { retB = 1.0; ret += 2.0 * aDotB + bMag; } else if (aDotB >= 0.0) { retB = 0.0; } else { retB = -aDotB / bMag; ret += aDotB * retB; } } } else { retB = cMag + aDotC; retB -= bDotC; retA = retB - aDotB; if (retA <= 0.0) { retB = 0.0; retA = 1.0; ret += 2.0 * aDotC + cMag; } else { retB = bMag - 2.0 * bDotC; retB += cMag; if (retA >= retB) { retB = 1.0; retA = 0.0; ret += 2.0 * aDotB + bMag; } else { retB = retA / retB; retA = 1.0 - retB; ret += retB * (2.0 * aDotB + (bMag * retB + bDotC * retA)) + retA * (2.0 * aDotC + (bDotC * retB + cMag * retA)); } } } } if (baryX != nullptr) { *baryX = float(retA); } if (baryY != nullptr) { *baryY = float(retB); } return ret; } bool TriSphereOverlap(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2) { return TriPointSqrDist(sphere.position, trivert0, trivert1, trivert2, nullptr, nullptr) <= sphere.radius * sphere.radius; } bool TriSphereIntersection(const zeus::CSphere& sphere, const zeus::CVector3f& trivert0, const zeus::CVector3f& trivert1, const zeus::CVector3f& trivert2, zeus::CVector3f& point, zeus::CVector3f& normal) { float baryX; float baryY; if (TriPointSqrDist(sphere.position, trivert0, trivert1, trivert2, &baryX, &baryY) > sphere.radius * sphere.radius) { return false; } const zeus::CVector3f barys(baryX, baryY, 1.f - (baryX + baryY)); point = zeus::baryToWorld(trivert2, trivert1, trivert0, barys); if (baryX == 0.f || baryX == 1.f || baryY == 0.f || baryY == 1.f || barys.z() == 0.f || barys.z() == 1.f) { normal = -sphere.getSurfaceNormal(point); } else { normal = (trivert1 - trivert0).cross(trivert2 - trivert0).normalized(); } return true; } bool BoxLineTest(const zeus::CAABox& aabb, const zeus::CVector3f& point, const zeus::CVector3f& dir, float& tMin, float& tMax, int& axis, bool& sign) { tMin = -999999.f; tMax = 999999.f; for (size_t i = 0; i < 3; ++i) { if (dir[i] == 0.f) { if (point[i] < aabb.min[i] || point[i] > aabb.max[i]) { return false; } } const float dirRecip = 1.f / dir[i]; float tmpMin; float tmpMax; if (dir[i] < 0.f) { tmpMin = (aabb.max[i] - point[i]) * dirRecip; tmpMax = (aabb.min[i] - point[i]) * dirRecip; } else { tmpMin = (aabb.min[i] - point[i]) * dirRecip; tmpMax = (aabb.max[i] - point[i]) * dirRecip; } if (tmpMin > tMin) { sign = dir[i] < 0.f; axis = i; tMin = tmpMin; } if (tmpMax < tMax) { tMax = tmpMax; } } return tMin <= tMax; } bool LineCircleIntersection2d(const zeus::CVector3f& point, const zeus::CVector3f& dir, const zeus::CSphere& sphere, int axis1, int axis2, float& d) { const zeus::CVector3f delta = sphere.position - point; const zeus::CVector2f deltaVec(delta[axis1], delta[axis2]); const zeus::CVector2f dirVec(dir[axis1], dir[axis2]); const float dirVecMag = dirVec.magnitude(); if (dirVecMag < FLT_EPSILON) { return false; } const float deltaVecDot = deltaVec.dot(dirVec / dirVecMag); const float deltaVecMagSq = deltaVec.magSquared(); const float sphereRadSq = sphere.radius * sphere.radius; if (deltaVecDot < 0.f && deltaVecMagSq > sphereRadSq) { return false; } const float tSq = sphereRadSq - (deltaVecMagSq - deltaVecDot * deltaVecDot); if (tSq < 0.f) { return false; } const float t = std::sqrt(tSq); d = (deltaVecMagSq > sphereRadSq) ? deltaVecDot - t : deltaVecDot + t; d /= dirVecMag; return true; } bool MovingSphereAABox(const zeus::CSphere& sphere, const zeus::CAABox& aabb, const zeus::CVector3f& dir, double& dOut, zeus::CVector3f& point, zeus::CVector3f& normal) { const zeus::CAABox expAABB(aabb.min - sphere.radius, aabb.max + sphere.radius); float tMin; float tMax; int axis; bool sign; if (!BoxLineTest(expAABB, sphere.position, dir, tMin, tMax, axis, sign)) { return false; } point = sphere.position + tMin * dir; const int nextAxis1 = (axis + 1) % 3; // r0 const int nextAxis2 = (axis + 2) % 3; // r5 const bool inMin1 = point[nextAxis1] >= aabb.min[nextAxis1]; // r6 const bool inMax1 = point[nextAxis1] <= aabb.max[nextAxis1]; // r8 const bool inBounds1 = inMin1 && inMax1; // r9 const bool inMin2 = point[nextAxis2] >= aabb.min[nextAxis2]; // r7 const bool inMax2 = point[nextAxis2] <= aabb.max[nextAxis2]; // r4 const bool inBounds2 = inMin2 && inMax2; // r8 if (inBounds1 && inBounds2) { if (tMin < 0.f || tMin > dOut) { return false; } normal[axis] = sign ? 1.f : -1.f; dOut = tMin; point -= normal * sphere.radius; return true; } if (!inBounds1 && !inBounds2) { const int pointFlags = (1 << axis) * sign | (1 << nextAxis1) * inMin1 | (1 << nextAxis2) * inMin2; const zeus::CVector3f aabbPoint = aabb.getPoint(pointFlags); float d; if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint, sphere.radius), sphere.position, dir, dOut, d, point)) { int useAxis = -1; for (int i = 0; i < 3; ++i) { if ((pointFlags & (1 << i)) ? aabbPoint[i] > point[i] : aabbPoint[i] < point[i]) { useAxis = i; break; } } if (useAxis == -1) { normal = (point - aabbPoint).normalized(); point -= sphere.radius * normal; return true; } const int useAxisNext1 = (useAxis + 1) % 3; const int useAxisNext2 = (useAxis + 2) % 3; float d; if (CollisionUtil::LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint, sphere.radius), useAxisNext1, useAxisNext2, d) && d > 0.f && d < dOut) { if (point[useAxis] > aabb.max[useAxis]) { const int useAxisBit = 1 << useAxis; if (pointFlags & useAxisBit) { return false; } const zeus::CVector3f aabbPoint1 = aabb.getPoint(pointFlags | useAxisBit); if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint1, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint1).normalized(); point -= normal * sphere.radius; return true; } else { return false; } } else if (point[useAxis] < aabb.min[useAxis]) { const int useAxisBit = 1 << useAxis; if (!(pointFlags & useAxisBit)) { return false; } const zeus::CVector3f aabbPoint1 = aabb.getPoint(pointFlags ^ useAxisBit); if (CollisionUtil::RaySphereIntersection(zeus::CSphere(aabbPoint1, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint1).normalized(); point -= normal * sphere.radius; return true; } else { return false; } } else { normal = point - aabbPoint; normal.normalize(); point -= normal * sphere.radius; return true; } } } else { int reverseCount = 0; float dMin = 1.0e10f; int minAxis = 0; for (int i = 0; i < 3; ++i) { if (std::fabs(dir[i]) > FLT_EPSILON) { const bool pointMax = (pointFlags & (1 << i)) != 0; if (pointMax != (dir[i] > 0.f)) { ++reverseCount; const float d = 1.f / dir[i] * ((pointMax ? aabb.max[i] : aabb.min[i]) - sphere.position[i]); if (d < 0.f) { return false; } if (d < dMin) { dMin = d; minAxis = i; } } } } if (reverseCount < 2) { return false; } const int useAxisNext1 = (minAxis + 1) % 3; const int useAxisNext2 = (minAxis + 2) % 3; float d; if (CollisionUtil::LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint, sphere.radius), useAxisNext1, useAxisNext2, d) && d > 0.f && d < dOut) { point = sphere.position + d * dir; if (point[minAxis] > aabb.max[minAxis]) { return false; } if (point[minAxis] < aabb.min[minAxis]) { return false; } dOut = d; normal = point - aabbPoint; normal.normalize(); point -= sphere.radius * normal; return true; } else { return false; } } } const bool useNextAxis1 = inBounds1 ? nextAxis2 : nextAxis1; const bool useNextAxis2 = inBounds1 ? nextAxis1 : nextAxis2; const int pointFlags = ((1 << int(useNextAxis1)) * (inBounds1 ? inMin2 : inMin1)) | ((1 << axis) * sign); const zeus::CVector3f aabbPoint2 = aabb.getPoint(pointFlags); float d; if (LineCircleIntersection2d(sphere.position, dir, zeus::CSphere(aabbPoint2, sphere.radius), axis, useNextAxis1, d) && d > 0.f && d < dOut) { point = sphere.position + d * dir; if (point[useNextAxis2] > aabb.max[useNextAxis2]) { const zeus::CVector3f aabbPoint3 = aabb.getPoint(pointFlags | (1 << int(useNextAxis2))); if (point[useNextAxis2] < expAABB.max[useNextAxis2]) { if (RaySphereIntersection(zeus::CSphere(aabbPoint3, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint3).normalized(); point -= sphere.radius * normal; return true; } } return false; } if (point[useNextAxis2] < aabb.min[useNextAxis2]) { if (point[useNextAxis2] > expAABB.min[useNextAxis2]) { if (RaySphereIntersection(zeus::CSphere(aabbPoint2, sphere.radius), sphere.position, dir, dOut, d, point)) { dOut = d; normal = (point - aabbPoint2).normalized(); point -= sphere.radius * normal; return true; } } return false; } else { dOut = d; normal = point - aabbPoint2; normal.normalize(); point -= sphere.radius * normal; return true; } } return false; } bool AABox_AABox_Moving(const zeus::CAABox& aabb0, const zeus::CAABox& aabb1, const zeus::CVector3f& dir, double& d, zeus::CVector3f& point, zeus::CVector3f& normal) { zeus::CVector3d vecMin(-FLT_MAX); zeus::CVector3d vecMax(FLT_MAX); for (size_t i = 0; i < 3; ++i) { if (std::fabs(dir[i]) < FLT_EPSILON) { if (aabb0.min[i] >= aabb1.min[i] && aabb0.min[i] <= aabb1.max[i]) { continue; } if (aabb0.max[i] >= aabb1.min[i] && aabb0.max[i] <= aabb1.max[i]) { continue; } if (aabb0.min[i] < aabb1.min[i] && aabb0.max[i] > aabb1.max[i]) { continue; } return false; } else { if (aabb0.max[i] < aabb1.min[i] && dir[i] > 0.f) { vecMin[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } else if (aabb1.max[i] < aabb0.min[i] && dir[i] < 0.f) { vecMin[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } else if (aabb1.max[i] > aabb0.min[i] && dir[i] < 0.f) { vecMin[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } else if (aabb0.max[i] > aabb1.min[i] && dir[i] > 0.f) { vecMin[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } if (aabb1.max[i] > aabb0.min[i] && dir[i] > 0.f) { vecMax[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } else if (aabb0.max[i] > aabb1.min[i] && dir[i] < 0.f) { vecMax[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } else if (aabb0.max[i] < aabb1.min[i] && dir[i] < 0.f) { vecMax[i] = (aabb1.min[i] - aabb0.max[i]) / dir[i]; } else if (aabb1.max[i] < aabb0.min[i] && dir[i] > 0.f) { vecMax[i] = (aabb1.max[i] - aabb0.min[i]) / dir[i]; } } } int maxAxis = 0; if (vecMin[1] > vecMin[0]) { maxAxis = 1; } if (vecMin[2] > vecMin[maxAxis]) { maxAxis = 2; } const double minMax = std::min(std::min(vecMax[2], vecMax[1]), vecMax[0]); if (vecMin[maxAxis] > minMax) { return false; } d = vecMin[maxAxis]; normal = zeus::skZero3f; normal[maxAxis] = dir[maxAxis] > 0.f ? -1.f : 1.f; for (size_t i = 0; i < 3; ++i) { point[i] = dir[i] > 0.f ? aabb0.max[i] : aabb0.min[i]; } point += float(d) * dir; return true; } void AddAverageToFront(const CCollisionInfoList& in, CCollisionInfoList& out) { if (in.GetCount() > 1) { zeus::CVector3f pointAccum; zeus::CVector3f normAccum; for (const CCollisionInfo& info : in) { pointAccum += info.GetPoint(); normAccum += info.GetNormalLeft(); } if (normAccum.canBeNormalized()) { out.Add(CCollisionInfo(pointAccum / float(in.GetCount()), in.GetItem(0).GetMaterialRight(), in.GetItem(0).GetMaterialLeft(), normAccum.normalized()), false); } } for (const CCollisionInfo& info : in) { out.Add(info, false); } } } // namespace metaforce::CollisionUtil