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.
 
 
 
 
 
 

700 lines
22 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.2019.08.13
#pragma once
#include <Mathematics/Logger.h>
#include <Mathematics/ArbitraryPrecision.h>
#include <Mathematics/Matrix3x3.h>
// A quadric surface is defined implicitly by
//
// 0 = a0 + a1*x[0] + a2*x[1] + a3*x[2] + a4*x[0]^2 + a5*x[0]*x[1] +
// a6*x[0]*x[2] + a7*x[1]^2 + a8*x[1]*x[2] + a9*x[2]^2
//
// = a0 + [a1 a2 a3]*X + X^T*[a4 a5/2 a6/2]*X
// [a5/2 a7 a8/2]
// [a6/2 a8/2 a9 ]
// = C + B^T*X + X^T*A*X
//
// The matrix A is symmetric.
namespace gte
{
template <typename Real>
class QuadricSurface
{
public:
// Construction and destruction. The default constructor sets all
// coefficients to zero.
QuadricSurface()
{
mCoefficient.fill((Real)0);
mC = (Real)0;
mB.MakeZero();
mA.MakeZero();
}
QuadricSurface(std::array<Real, 10> const& coefficient)
:
mCoefficient(coefficient)
{
mC = mCoefficient[0];
mB[0] = mCoefficient[1];
mB[1] = mCoefficient[2];
mB[2] = mCoefficient[3];
mA(0, 0) = mCoefficient[4];
mA(0, 1) = (Real)0.5 * mCoefficient[5];
mA(0, 2) = (Real)0.5 * mCoefficient[6];
mA(1, 0) = mA(0, 1);
mA(1, 1) = mCoefficient[7];
mA(1, 2) = (Real)0.5 * mCoefficient[8];
mA(2, 0) = mA(0, 2);
mA(2, 1) = mA(1, 2);
mA(2, 2) = mCoefficient[9];
}
// Member access.
inline std::array<Real, 10> const& GetCoefficients() const
{
return mCoefficient;
}
inline Real const& GetC() const
{
return mC;
}
inline Vector3<Real> const& GetB() const
{
return mB;
}
inline Matrix3x3<Real> const& GetA() const
{
return mA;
}
// Evaluate the function.
Real F(Vector3<Real> const& position) const
{
Real f = Dot(position, mA * position + mB) + mC;
return f;
}
// Evaluate the first-order partial derivatives (gradient).
Real FX(Vector3<Real> const& position) const
{
Real sum = mA(0, 0) * position[0] + mA(0, 1) * position[1] + mA(0, 2) * position[2];
Real fx = (Real)2 * sum + mB[0];
return fx;
}
Real FY(Vector3<Real> const& position) const
{
Real sum = mA(1, 0) * position[0] + mA(1, 1) * position[1] + mA(1, 2) * position[2];
Real fy = (Real)2 * sum + mB[1];
return fy;
}
Real FZ(Vector3<Real> const& position) const
{
Real sum = mA(2, 0) * position[0] + mA(2, 1) * position[1] + mA(2, 2) * position[2];
Real fz = (Real)2 * sum + mB[2];
return fz;
}
// Evaluate the second-order partial derivatives (Hessian).
Real FXX(Vector3<Real> const&) const
{
Real fxx = (Real)2 * mA(0, 0);
return fxx;
}
Real FXY(Vector3<Real> const&) const
{
Real fxy = (Real)2 * mA(0, 1);
return fxy;
}
Real FXZ(Vector3<Real> const&) const
{
Real fxz = (Real)2 * mA(0, 2);
return fxz;
}
Real FYY(Vector3<Real> const&) const
{
Real fyy = (Real)2 * mA(1, 1);
return fyy;
}
Real FYZ(Vector3<Real> const&) const
{
Real fyz = (Real)2 * mA(1, 2);
return fyz;
}
Real FZZ(Vector3<Real> const&) const
{
Real fzz = (Real)2 * mA(2, 2);
return fzz;
}
// Classification of the quadric. The implementation uses exact
// rational arithmetic to avoid misclassification due to
// floating-point rounding errors.
enum Classification
{
QT_NONE,
QT_POINT,
QT_LINE,
QT_PLANE,
QT_TWO_PLANES,
QT_PARABOLIC_CYLINDER,
QT_ELLIPTIC_CYLINDER,
QT_HYPERBOLIC_CYLINDER,
QT_ELLIPTIC_PARABOLOID,
QT_HYPERBOLIC_PARABOLOID,
QT_ELLIPTIC_CONE,
QT_HYPERBOLOID_ONE_SHEET,
QT_HYPERBOLOID_TWO_SHEETS,
QT_ELLIPSOID
};
Classification GetClassification() const
{
// Convert the coefficients to their rational representations and
// compute various derived quantities.
RReps reps(mCoefficient);
// Use Sturm sequences to determine the signs of the roots.
int positiveRoots, negativeRoots, zeroRoots;
GetRootSigns(reps, positiveRoots, negativeRoots, zeroRoots);
// Classify the solution set to the equation.
Classification type = QT_NONE;
switch (zeroRoots)
{
case 0:
type = ClassifyZeroRoots0(reps, positiveRoots);
break;
case 1:
type = ClassifyZeroRoots1(reps, positiveRoots);
break;
case 2:
type = ClassifyZeroRoots2(reps, positiveRoots);
break;
case 3:
type = ClassifyZeroRoots3(reps);
break;
}
return type;
}
private:
typedef BSRational<UIntegerAP32> Rational;
typedef Vector<3, Rational> RVector3;
class RReps
{
public:
RReps(std::array<Real, 10> const& coefficient)
{
Rational half = (Real)0.5;
c = Rational(coefficient[0]);
b0 = Rational(coefficient[1]);
b1 = Rational(coefficient[2]);
b2 = Rational(coefficient[3]);
a00 = Rational(coefficient[4]);
a01 = half * Rational(coefficient[5]);
a02 = half * Rational(coefficient[6]);
a11 = Rational(coefficient[7]);
a12 = half * Rational(coefficient[8]);
a22 = Rational(coefficient[9]);
sub00 = a11 * a22 - a12 * a12;
sub01 = a01 * a22 - a12 * a02;
sub02 = a01 * a12 - a02 * a11;
sub11 = a00 * a22 - a02 * a02;
sub12 = a00 * a12 - a02 * a01;
sub22 = a00 * a11 - a01 * a01;
k0 = a00 * sub00 - a01 * sub01 + a02 * sub02;
k1 = sub00 + sub11 + sub22;
k2 = a00 + a11 + a22;
k3 = Rational(0);
k4 = Rational(0);
k5 = Rational(0);
}
// Quadratic coefficients.
Rational a00, a01, a02, a11, a12, a22, b0, b1, b2, c;
// 2-by-2 determinants
Rational sub00, sub01, sub02, sub11, sub12, sub22;
// Characteristic polynomial L^3 - k2 * L^2 + k1 * L - k0.
Rational k0, k1, k2;
// For Sturm sequences.
Rational k3, k4, k5;
};
static void GetRootSigns(RReps& reps, int& positiveRoots, int& negativeRoots, int& zeroRoots)
{
// Use Sturm sequences to determine the signs of the roots.
int signChangeMI, signChange0, signChangePI, distinctNonzeroRoots;
std::array<Rational, 4> value;
Rational const zero(0);
if (reps.k0 != zero)
{
reps.k3 = Rational(2, 9) * reps.k2 * reps.k2 - Rational(2, 3) * reps.k1;
reps.k4 = reps.k0 - Rational(1, 9) * reps.k1 * reps.k2;
if (reps.k3 != zero)
{
reps.k5 = -(reps.k1 + ((Rational(2) * reps.k2 * reps.k3 +
Rational(3) * reps.k4) * reps.k4) / (reps.k3 * reps.k3));
value[0] = 1;
value[1] = -reps.k3;
value[2] = reps.k5;
signChangeMI = 1 + GetSignChanges(3, value);
value[0] = -reps.k0;
value[1] = reps.k1;
value[2] = reps.k4;
value[3] = reps.k5;
signChange0 = GetSignChanges(4, value);
value[0] = 1;
value[1] = reps.k3;
value[2] = reps.k5;
signChangePI = GetSignChanges(3, value);
}
else
{
value[0] = -reps.k0;
value[1] = reps.k1;
value[2] = reps.k4;
signChange0 = GetSignChanges(3, value);
value[0] = 1;
value[1] = reps.k4;
signChangePI = GetSignChanges(2, value);
signChangeMI = 1 + signChangePI;
}
positiveRoots = signChange0 - signChangePI;
LogAssert(positiveRoots >= 0, "Unexpected condition.");
negativeRoots = signChangeMI - signChange0;
LogAssert(negativeRoots >= 0, "Unexpected condition.");
zeroRoots = 0;
distinctNonzeroRoots = positiveRoots + negativeRoots;
if (distinctNonzeroRoots == 2)
{
if (positiveRoots == 2)
{
positiveRoots = 3;
}
else if (negativeRoots == 2)
{
negativeRoots = 3;
}
else
{
// One root is positive and one is negative. One root
// has multiplicity 2, the other of multiplicity 1.
// Distinguish between the two cases by computing the
// sign of the polynomial at the inflection point
// L = k2/3.
Rational X = Rational(1, 3) * reps.k2;
Rational poly = X * (X * (X - reps.k2) + reps.k1) - reps.k0;
if (poly > zero)
{
positiveRoots = 2;
}
else
{
negativeRoots = 2;
}
}
}
else if (distinctNonzeroRoots == 1)
{
// Root of multiplicity 3.
if (positiveRoots == 1)
{
positiveRoots = 3;
}
else
{
negativeRoots = 3;
}
}
return;
}
if (reps.k1 != zero)
{
reps.k3 = Rational(1, 4) * reps.k2 * reps.k2 - reps.k1;
value[0] = -1;
value[1] = reps.k3;
signChangeMI = 1 + GetSignChanges(2, value);
value[0] = reps.k1;
value[1] = -reps.k2;
value[2] = reps.k3;
signChange0 = GetSignChanges(3, value);
value[0] = 1;
value[1] = reps.k3;
signChangePI = GetSignChanges(2, value);
positiveRoots = signChange0 - signChangePI;
LogAssert(positiveRoots >= 0, "Unexpected condition.");
negativeRoots = signChangeMI - signChange0;
LogAssert(negativeRoots >= 0, "Unexpected condition.");
zeroRoots = 1;
distinctNonzeroRoots = positiveRoots + negativeRoots;
if (distinctNonzeroRoots == 1)
{
positiveRoots = 2;
}
return;
}
if (reps.k2 != zero)
{
zeroRoots = 2;
if (reps.k2 > zero)
{
positiveRoots = 1;
negativeRoots = 0;
}
else
{
positiveRoots = 0;
negativeRoots = 1;
}
return;
}
positiveRoots = 0;
negativeRoots = 0;
zeroRoots = 3;
}
static int GetSignChanges(int quantity, std::array<Rational, 4> const& value)
{
int signChanges = 0;
Rational const zero(0);
Rational prev = value[0];
for (int i = 1; i < quantity; ++i)
{
Rational next = value[i];
if (next != zero)
{
if (prev * next < zero)
{
++signChanges;
}
prev = next;
}
}
return signChanges;
}
static Classification ClassifyZeroRoots0(RReps const& reps, int positiveRoots)
{
// The inverse matrix is
// +- -+
// | sub00 -sub01 sub02 |
// | -sub01 sub11 -sub12 | * (1/det)
// | sub02 -sub12 sub22 |
// +- -+
Rational fourDet = Rational(4) * reps.k0;
Rational qForm = reps.b0 * (reps.sub00 * reps.b0 -
reps.sub01 * reps.b1 + reps.sub02 * reps.b2) -
reps.b1 * (reps.sub01 * reps.b0 - reps.sub11 * reps.b1 +
reps.sub12 * reps.b2) + reps.b2 * (reps.sub02 * reps.b0 -
reps.sub12 * reps.b1 + reps.sub22 * reps.b2);
Rational r = Rational(1, 4) * qForm / fourDet - reps.c;
Rational const zero(0);
if (r > zero)
{
if (positiveRoots == 3)
{
return QT_ELLIPSOID;
}
else if (positiveRoots == 2)
{
return QT_HYPERBOLOID_ONE_SHEET;
}
else if (positiveRoots == 1)
{
return QT_HYPERBOLOID_TWO_SHEETS;
}
else
{
return QT_NONE;
}
}
else if (r < zero)
{
if (positiveRoots == 3)
{
return QT_NONE;
}
else if (positiveRoots == 2)
{
return QT_HYPERBOLOID_TWO_SHEETS;
}
else if (positiveRoots == 1)
{
return QT_HYPERBOLOID_ONE_SHEET;
}
else
{
return QT_ELLIPSOID;
}
}
// else r == 0
if (positiveRoots == 3 || positiveRoots == 0)
{
return QT_POINT;
}
return QT_ELLIPTIC_CONE;
}
static Classification ClassifyZeroRoots1(RReps const& reps, int positiveRoots)
{
// Generate an orthonormal set {p0,p1,p2}, where p0 is an
// eigenvector of A corresponding to eigenvalue zero.
RVector3 P0, P1, P2;
Rational const zero(0);
if (reps.sub00 != zero || reps.sub01 != zero || reps.sub02 != zero)
{
// Rows 1 and 2 are linearly independent.
P0 = { reps.sub00, -reps.sub01, reps.sub02 };
P1 = { reps.a01, reps.a11, reps.a12 };
P2 = Cross(P0, P1);
return ClassifyZeroRoots1(reps, positiveRoots, P0, P1, P2);
}
if (reps.sub01 != zero || reps.sub11 != zero || reps.sub12 != zero)
{
// Rows 2 and 0 are linearly independent.
P0 = { -reps.sub01, reps.sub11, -reps.sub12 };
P1 = { reps.a02, reps.a12, reps.a22 };
P2 = Cross(P0, P1);
return ClassifyZeroRoots1(reps, positiveRoots, P0, P1, P2);
}
// Rows 0 and 1 are linearly independent.
P0 = { reps.sub02, -reps.sub12, reps.sub22 };
P1 = { reps.a00, reps.a01, reps.a02 };
P2 = Cross(P0, P1);
return ClassifyZeroRoots1(reps, positiveRoots, P0, P1, P2);
}
static Classification ClassifyZeroRoots1(RReps const& reps, int positiveRoots,
RVector3 const& P0, RVector3 const& P1, RVector3 const& P2)
{
Rational const zero(0);
Rational e0 = P0[0] * reps.b0 + P0[1] * reps.b1 + P0[2] * reps.b2;
if (e0 != zero)
{
if (positiveRoots == 1)
{
return QT_HYPERBOLIC_PARABOLOID;
}
else
{
return QT_ELLIPTIC_PARABOLOID;
}
}
// Matrix F.
Rational f11 = P1[0] * (reps.a00 * P1[0] + reps.a01 * P1[1] +
reps.a02 * P1[2]) + P1[1] * (reps.a01 * P1[0] +
reps.a11 * P1[1] + reps.a12 * P1[2]) + P1[2] * (
reps.a02 * P1[0] + reps.a12 * P1[1] + reps.a22 * P1[2]);
Rational f12 = P2[0] * (reps.a00 * P1[0] + reps.a01 * P1[1] +
reps.a02 * P1[2]) + P2[1] * (reps.a01 * P1[0] +
reps.a11 * P1[1] + reps.a12 * P1[2]) + P2[2] * (
reps.a02 * P1[0] + reps.a12 * P1[1] + reps.a22 * P1[2]);
Rational f22 = P2[0] * (reps.a00 * P2[0] + reps.a01 * P2[1] +
reps.a02 * P2[2]) + P2[1] * (reps.a01 * P2[0] +
reps.a11 * P2[1] + reps.a12 * P2[2]) + P2[2] * (
reps.a02 * P2[0] + reps.a12 * P2[1] + reps.a22 * P2[2]);
// Vector g.
Rational g1 = P1[0] * reps.b0 + P1[1] * reps.b1 + P1[2] * reps.b2;
Rational g2 = P2[0] * reps.b0 + P2[1] * reps.b1 + P2[2] * reps.b2;
// Compute g^T*F^{-1}*g/4 - c.
Rational fourDet = Rational(4) * (f11 * f22 - f12 * f12);
Rational r = (g1 * (f22 * g1 - f12 * g2) + g2 * (f11 * g2 - f12 * g1)) / fourDet - reps.c;
if (r > zero)
{
if (positiveRoots == 2)
{
return QT_ELLIPTIC_CYLINDER;
}
else if (positiveRoots == 1)
{
return QT_HYPERBOLIC_CYLINDER;
}
else
{
return QT_NONE;
}
}
else if (r < zero)
{
if (positiveRoots == 2)
{
return QT_NONE;
}
else if (positiveRoots == 1)
{
return QT_HYPERBOLIC_CYLINDER;
}
else
{
return QT_ELLIPTIC_CYLINDER;
}
}
// else r == 0
return (positiveRoots == 1 ? QT_TWO_PLANES : QT_LINE);
}
static Classification ClassifyZeroRoots2(const RReps& reps, int positiveRoots)
{
// Generate an orthonormal set {p0,p1,p2}, where p0 and p1 are
// eigenvectors of A corresponding to eigenvalue zero. The vector
// p2 is an eigenvector of A corresponding to eigenvalue c2.
Rational const zero(0);
RVector3 P0, P1, P2;
if (reps.a00 != zero || reps.a01 != zero || reps.a02 != zero)
{
// row 0 is not zero
P2 = { reps.a00, reps.a01, reps.a02 };
}
else if (reps.a01 != zero || reps.a11 != zero || reps.a12 != zero)
{
// row 1 is not zero
P2 = { reps.a01, reps.a11, reps.a12 };
}
else
{
// row 2 is not zero
P2 = { reps.a02, reps.a12, reps.a22 };
}
if (P2[0] != zero)
{
P1[0] = P2[1];
P1[1] = -P2[0];
P1[2] = zero;
}
else
{
P1[0] = zero;
P1[1] = P2[2];
P1[2] = -P2[1];
}
P0 = Cross(P1, P2);
return ClassifyZeroRoots2(reps, positiveRoots, P0, P1, P2);
}
static Classification ClassifyZeroRoots2(RReps const& reps, int positiveRoots,
RVector3 const& P0, RVector3 const& P1, RVector3 const& P2)
{
Rational const zero(0);
Rational e0 = P0[0] * reps.b0 + P0[1] * reps.b1 + P0[2] * reps.b1;
if (e0 != zero)
{
return QT_PARABOLIC_CYLINDER;
}
Rational e1 = P1[0] * reps.b0 + P1[1] * reps.b1 + P1[2] * reps.b1;
if (e1 != zero)
{
return QT_PARABOLIC_CYLINDER;
}
Rational f1 = reps.k2 * Dot(P2, P2);
Rational e2 = P2[0] * reps.b0 + P2[1] * reps.b1 + P2[2] * reps.b1;
Rational r = e2 * e2 / (Rational(4) * f1) - reps.c;
if (r > zero)
{
if (positiveRoots == 1)
{
return QT_TWO_PLANES;
}
else
{
return QT_NONE;
}
}
else if (r < zero)
{
if (positiveRoots == 1)
{
return QT_NONE;
}
else
{
return QT_TWO_PLANES;
}
}
// else r == 0
return QT_PLANE;
}
static Classification ClassifyZeroRoots3(RReps const& reps)
{
Rational const zero(0);
if (reps.b0 != zero || reps.b1 != zero || reps.b2 != zero)
{
return QT_PLANE;
}
return QT_NONE;
}
std::array<Real, 10> mCoefficient;
Real mC;
Vector3<Real> mB;
Matrix3x3<Real> mA;
};
}