// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // Version: 4.0.2019.08.13 #pragma once #include #include #include // Compute the distance from a point to a hyperellipsoid. In 2D, this is a // point-ellipse distance query. In 3D, this is a point-ellipsoid distance // query. The following document describes the algorithm. // https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf // The hyperellipsoid can have arbitrary center and orientation; that is, it // does not have to be axis-aligned with center at the origin. // // For the 2D query, // Vector2 point; // initialized to something // Ellipse2 ellipse; // initialized to something // DCPPoint2Ellipse2 query; // auto result = query(point, ellipse); // Real distance = result.distance; // Vector2 closestEllipsePoint = result.closest; // // For the 3D query, // Vector3 point; // initialized to something // Ellipsoid3 ellipsoid; // initialized to something // DCPPoint3Ellipsoid3 query; // auto result = query(point, ellipsoid); // Real distance = result.distance; // Vector3 closestEllipsoidPoint = result.closest; namespace WwiseGTE { template class DCPQuery, Hyperellipsoid> { public: struct Result { Real distance, sqrDistance; Vector closest; }; // The query for any hyperellipsoid. Result operator()(Vector const& point, Hyperellipsoid const& hyperellipsoid) { Result result; // Compute the coordinates of Y in the hyperellipsoid coordinate // system. Vector diff = point - hyperellipsoid.center; Vector y; for (int i = 0; i < N; ++i) { y[i] = Dot(diff, hyperellipsoid.axis[i]); } // Compute the closest hyperellipsoid point in the axis-aligned // coordinate system. Vector x; result.sqrDistance = SqrDistance(hyperellipsoid.extent, y, x); result.distance = std::sqrt(result.sqrDistance); // Convert back to the original coordinate system. result.closest = hyperellipsoid.center; for (int i = 0; i < N; ++i) { result.closest += x[i] * hyperellipsoid.axis[i]; } return result; } // The 'hyperellipsoid' is assumed to be axis-aligned and centered at the // origin , so only the extent[] values are used. Result operator()(Vector const& point, Vector const& extent) { Result result; result.sqrDistance = SqrDistance(extent, point, result.closest); result.distance = std::sqrt(result.sqrDistance); return result; } private: // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with no // constraints on the orderind of the e[d]. The query point is // (y[0],...,y[N-1]) with no constraints on the signs of the components. // The function returns the squared distance from the query point to the // hyperellipsoid. It also computes the hyperellipsoid point // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]). Real SqrDistance(Vector const& e, Vector const& y, Vector& x) { // Determine negations for y to the first octant. std::array negate; int i, j; for (i = 0; i < N; ++i) { negate[i] = (y[i] < (Real)0); } // Determine the axis order for decreasing extents. std::array, N> permute; for (i = 0; i < N; ++i) { permute[i].first = -e[i]; permute[i].second = i; } std::sort(permute.begin(), permute.end()); std::array invPermute; for (i = 0; i < N; ++i) { invPermute[permute[i].second] = i; } Vector locE, locY; for (i = 0; i < N; ++i) { j = permute[i].second; locE[i] = e[j]; locY[i] = std::fabs(y[j]); } Vector locX; Real sqrDistance = SqrDistanceSpecial(locE, locY, locX); // Restore the axis order and reflections. for (i = 0; i < N; ++i) { j = invPermute[i]; if (negate[i]) { locX[j] = -locX[j]; } x[i] = locX[j]; } return sqrDistance; } // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with the e[d] // positive and nonincreasing: e[d] >= e[d + 1] for all d. The query // point is (y[0],...,y[N-1]) with y[d] >= 0 for all d. The function // returns the squared distance from the query point to the // hyperellipsoid. It also computes the hyperellipsoid point // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]), where // x[d] >= 0 for all d. Real SqrDistanceSpecial(Vector const& e, Vector const& y, Vector& x) { Real sqrDistance = (Real)0; Vector ePos, yPos, xPos; int numPos = 0; int i; for (i = 0; i < N; ++i) { if (y[i] > (Real)0) { ePos[numPos] = e[i]; yPos[numPos] = y[i]; ++numPos; } else { x[i] = (Real)0; } } if (y[N - 1] > (Real)0) { sqrDistance = Bisector(numPos, ePos, yPos, xPos); } else // y[N-1] = 0 { Vector numer, denom; Real eNm1Sqr = e[N - 1] * e[N - 1]; for (i = 0; i < numPos; ++i) { numer[i] = ePos[i] * yPos[i]; denom[i] = ePos[i] * ePos[i] - eNm1Sqr; } bool inSubHyperbox = true; for (i = 0; i < numPos; ++i) { if (numer[i] >= denom[i]) { inSubHyperbox = false; break; } } bool inSubHyperellipsoid = false; if (inSubHyperbox) { // yPos[] is inside the axis-aligned bounding box of the // subhyperellipsoid. This intermediate test is designed // to guard against the division by zero when // ePos[i] == e[N-1] for some i. Vector xde; Real discr = (Real)1; for (i = 0; i < numPos; ++i) { xde[i] = numer[i] / denom[i]; discr -= xde[i] * xde[i]; } if (discr > (Real)0) { // yPos[] is inside the subhyperellipsoid. The // closest hyperellipsoid point has x[N-1] > 0. sqrDistance = (Real)0; for (i = 0; i < numPos; ++i) { xPos[i] = ePos[i] * xde[i]; Real diff = xPos[i] - yPos[i]; sqrDistance += diff * diff; } x[N - 1] = e[N - 1] * std::sqrt(discr); sqrDistance += x[N - 1] * x[N - 1]; inSubHyperellipsoid = true; } } if (!inSubHyperellipsoid) { // yPos[] is outside the subhyperellipsoid. The closest // hyperellipsoid point has x[N-1] == 0 and is on the // domain-boundary hyperellipsoid. x[N - 1] = (Real)0; sqrDistance = Bisector(numPos, ePos, yPos, xPos); } } // Fill in those x[] values that were not zeroed out initially. for (i = 0, numPos = 0; i < N; ++i) { if (y[i] > (Real)0) { x[i] = xPos[numPos]; ++numPos; } } return sqrDistance; } // The bisection algorithm to find the unique root of F(t). Real Bisector(int numComponents, Vector const& e, Vector const& y, Vector& x) { Vector z; Real sumZSqr = (Real)0; int i; for (i = 0; i < numComponents; ++i) { z[i] = y[i] / e[i]; sumZSqr += z[i] * z[i]; } if (sumZSqr == (Real)1) { // The point is on the hyperellipsoid. for (i = 0; i < numComponents; ++i) { x[i] = y[i]; } return (Real)0; } Real emin = e[numComponents - 1]; Vector pSqr, numerator; for (i = 0; i < numComponents; ++i) { Real p = e[i] / emin; pSqr[i] = p * p; numerator[i] = pSqr[i] * z[i]; } Real s = (Real)0, smin = z[numComponents - 1] - (Real)1, smax; if (sumZSqr < (Real)1) { // The point is strictly inside the hyperellipsoid. smax = (Real)0; } else { // The point is strictly outside the hyperellipsoid. smax = Length(numerator, true) - (Real)1; } // The use of 'double' is intentional in case Real is a BSNumber // or BSRational type. We want the bisections to terminate in a // reasonable/ amount of time. unsigned int const jmax = GTE_C_MAX_BISECTIONS_GENERIC; for (unsigned int j = 0; j < jmax; ++j) { s = (smin + smax) * (Real)0.5; if (s == smin || s == smax) { break; } Real g = (Real)-1; for (i = 0; i < numComponents; ++i) { Real ratio = numerator[i] / (s + pSqr[i]); g += ratio * ratio; } if (g > (Real)0) { smin = s; } else if (g < (Real)0) { smax = s; } else { break; } } Real sqrDistance = (Real)0; for (i = 0; i < numComponents; ++i) { x[i] = pSqr[i] * y[i] / (s + pSqr[i]); Real diff = x[i] - y[i]; sqrDistance += diff * diff; } return sqrDistance; } }; // Template aliases for convenience. template using DCPPointHyperellipsoid = DCPQuery, Hyperellipsoid>; template using DCPPoint2Ellipse2 = DCPPointHyperellipsoid<2, Real>; template using DCPPoint3Ellipsoid3 = DCPPointHyperellipsoid<3, Real>; }