You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

354 lines
12 KiB

// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2021
// 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.2020.09.29
#pragma once
#include <Mathematics/DCPQuery.h>
#include <Mathematics/Hyperellipsoid.h>
#include <Mathematics/Vector.h>
// 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<Real> point; // initialized to something
// Ellipse2<Real> ellipse; // initialized to something
// DCPPoint2Ellipse2<Real> query;
// auto result = query(point, ellipse);
// Real distance = result.distance;
// Vector2<Real> closestEllipsePoint = result.closest;
//
// For the 3D query,
// Vector3<Real> point; // initialized to something
// Ellipsoid3<Real> ellipsoid; // initialized to something
// DCPPoint3Ellipsoid3<Real> query;
// auto result = query(point, ellipsoid);
// Real distance = result.distance;
// Vector3<Real> closestEllipsoidPoint = result.closest;
namespace gte
{
template <int N, typename Real>
class DCPQuery<Real, Vector<N, Real>, Hyperellipsoid<N, Real>>
{
public:
struct Result
{
Real distance, sqrDistance;
Vector<N, Real> closest;
};
// The query for any hyperellipsoid.
Result operator()(Vector<N, Real> const& point,
Hyperellipsoid<N, Real> const& hyperellipsoid)
{
Result result;
// Compute the coordinates of Y in the hyperellipsoid coordinate
// system.
Vector<N, Real> diff = point - hyperellipsoid.center;
Vector<N, Real> 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<N, Real> 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<N, Real> const& point, Vector<N, Real> 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<N, Real> const& e,
Vector<N, Real> const& y, Vector<N, Real>& x)
{
// Determine negations for y to the first octant.
std::array<bool, N> 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<std::pair<Real, int>, 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<int, N> invPermute;
for (i = 0; i < N; ++i)
{
invPermute[permute[i].second] = i;
}
Vector<N, Real> locE, locY;
for (i = 0; i < N; ++i)
{
j = permute[i].second;
locE[i] = e[j];
locY[i] = std::fabs(y[j]);
}
Vector<N, Real> 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<N, Real> const& e,
Vector<N, Real> const& y, Vector<N, Real>& x)
{
Real sqrDistance = (Real)0;
Vector<N, Real> 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<N - 1, Real> 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<N - 1, Real> 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<N, Real> const& e,
Vector<N, Real> const& y, Vector<N, Real>& x)
{
Vector<N, Real> 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<N, Real> pSqr, numerator;
pSqr.MakeZero();
numerator.MakeZero();
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 <int N, typename Real>
using DCPPointHyperellipsoid = DCPQuery<Real, Vector<N, Real>, Hyperellipsoid<N, Real>>;
template <typename Real>
using DCPPoint2Ellipse2 = DCPPointHyperellipsoid<2, Real>;
template <typename Real>
using DCPPoint3Ellipsoid3 = DCPPointHyperellipsoid<3, Real>;
}