csgo-2018-source/mathlib/eigen.cpp
2021-07-24 21:11:47 -07:00

170 lines
5.9 KiB
C++

#include <basetypes.h>
#include <float.h>
#include "tier1/utlvector.h"
#include "eigen.h"
#include "mathlib/aabb.h"
static matrix3x4_t Transpose(const matrix3x4_t &a)
{
return matrix3x4_t(a[0][0],a[1][0],a[2][0],0,
a[0][1],a[1][1],a[2][1],0,
a[0][2],a[1][2],a[2][2],0 );
}
Quaternion Diagonalizer( const matrix3x4_t &A, Vector &d )
{
// A must be a symmetric matrix.
// returns quaternion q such that its corresponding matrix Q
// can be used to Diagonalize A
// Note, this routine has been adapted to valve's matrix conventions
// which is C-style row-major matrix using common mathtext (openglish) column conventions for
// representing transforms (rotation and position): v' = Mv
// Valve's quaternion conventions are the same that everybody uses novodex, d3d, ogl.
// Diagonal matrix D = Transpose(Q) * A * Q; and A = QT*D*Q
// The columns of Q are the eigenvectors D's diagonal is the eigenvalues
// As per 'column' convention if Q = q.getmatrix(); then Q*v = q*v*conj(q)
int maxsteps = 24; // certainly wont need that many.
int i;
Quaternion q( 0, 0, 0, 1 );
for ( i = 0; i < maxsteps; i++ )
{
matrix3x4_t Q ; // Q*v == q*v*conj(q)
QuaternionMatrix( q, Q );
matrix3x4_t D = Transpose( Q ) * A * Q; // A = Q*D*Q^T
Vector offdiag( D[1][2], D[0][2], D[0][1] ); // elements not on the diagonal
d = Vector( D[0][0], D[1][1], D[2][2] );
Vector om( fabsf( offdiag.x ), fabsf( offdiag.y ), fabsf( offdiag.z ) ); // mag of each offdiag elem
int k = ( om.x > om.y && om.x > om.z ) ? 0 : ( om.y > om.z ) ? 1 : 2; // index of largest element of offdiag
int k1 = ( k + 1 ) % 3;
int k2 = ( k + 2 ) % 3;
if ( offdiag[k] == 0.0f ) break; // diagonal already
float thet = ( D[k2][k2] - D[k1][k1] ) / ( 2.0f * offdiag[k] );
float sgn = ( thet > 0.0f ) ? 1.0f : -1.0f;
thet *= sgn; // make it positive
float t = sgn / ( thet + ( ( thet < 1.E6f ) ? sqrtf( thet * thet + 1.0f ) : thet ) ) ; // sign(T)/(|T|+sqrt(T^2+1))
float c = 1.0f / sqrtf( t * t + 1.0f ); // c= 1/(t^2+1) , t=s/c
if ( c == 1.0f ) break; // no room for improvement - reached machine precision.
Quaternion jr( 0, 0, 0, 0 ); // jacobi rotation for this iteration.
jr[k] = sgn * sqrtf( ( 1.0f - c ) / 2.0f ); // using 1/2 angle identity sin(a/2) = sqrt((1-cos(a))/2)
jr[k] *= -1.0f; // ??since our quat-to-matrix convention was for v*M instead of M*v
jr.w = sqrtf( 1.0f - jr[k] * jr[k] );
if ( jr.w == 1.0f ) break; // reached limits of floating point precision
QuaternionMult( q, jr, q ); //q = q*jr;
QuaternionNormalize( q );
}
return q;
}
//-----------------------------------------------------------------------------
// Computes the mean point of a set of points, used by ComputeCovariantMatrix
//-----------------------------------------------------------------------------
extern Vector ComputeMeanPoint( const Vector *pPointList, int nPointCount )
{
Vector vMean( 0.0f, 0.0f, 0.0f );
for ( int ii = 0; ii < nPointCount; ++ii )
{
vMean += pPointList[ii];
}
vMean /= static_cast< float >( nPointCount );
return vMean;
}
//-----------------------------------------------------------------------------
// Computes a covariance matrix for a set of points which measures spatial
// dispersion of the points against the mean of the points,
// the covariance matrix is symmetric and suitable for use in Diagonalizer()
//-----------------------------------------------------------------------------
void ComputeCovarianceMatrix( matrix3x4_t &covarianceMatrix, const Vector *pPointList, int nPointCount )
{
SetIdentityMatrix( covarianceMatrix );
if ( nPointCount <= 0 )
return;
const Vector vMean = ComputeMeanPoint( pPointList, nPointCount );
CUtlVector< Vector > skewedPointList;
skewedPointList.CopyArray( pPointList, nPointCount );
for ( int ii = 0; ii < nPointCount; ++ii )
{
skewedPointList[ ii ] -= vMean;
}
const float flPointCount = static_cast< float >( nPointCount );
for ( int ii = 0; ii < 3; ++ii )
{
for ( int jj = 0; jj < 3; ++jj )
{
float flCovariance = 0.0f;
for ( int kk = 0; kk < nPointCount; ++kk )
{
flCovariance += skewedPointList[kk][ii] * skewedPointList[kk][jj];
}
covarianceMatrix[ii][jj] = flCovariance / flPointCount;
}
}
}
//-----------------------------------------------------------------------------
// Computes the center and scale using qEigenVectors as the orientation to
// transform a unit cube at the origin to contain the specified point list,
// calls ComputeCovarianceMatrix(), Diagonalizer()
//-----------------------------------------------------------------------------
void ComputeExtents( Vector &vCenter, Vector &vScale, const Quaternion &qEigen, const Vector *pPointList, int nPointCount )
{
if ( nPointCount <= 0 )
return;
AABB_t bbox;
// Compute bounding box in inverse eigen space
const Quaternion qEigenInverse = QuaternionInvert( qEigen );
const matrix3x4_t mEigenInverse = QuaternionMatrix( qEigenInverse ); // VectorRotate with a quaternion does this each call
Vector vTmp;
VectorRotate( pPointList[0], mEigenInverse, vTmp );
bbox.SetToPoint( vTmp );
for ( int ii = 1; ii < nPointCount; ++ii )
{
VectorRotate( pPointList[ii], mEigenInverse, vTmp );
bbox |= vTmp;
}
VectorRotate( bbox.GetCenter(), qEigen, vCenter ); // Transform center back to eigen space
vScale = bbox.GetSize();
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
extern void ComputeBoundingBoxMatrix( matrix3x4_t &boundingBoxMatrix, const Vector *pPointList, int nPointCount )
{
matrix3x4_t covarianceMatrix;
ComputeCovarianceMatrix( covarianceMatrix, pPointList, nPointCount );
Vector vEigenValues;
const Quaternion qEigenVectors = Diagonalizer( covarianceMatrix, vEigenValues );
Vector vCenter( 0.0f, 0.0f, 0.0f );
Vector vScale( 1.0f, 1.0f, 1.0f );
ComputeExtents( vCenter, vScale, qEigenVectors, pPointList, nPointCount );
QuaternionMatrix( qEigenVectors, vCenter, vScale, boundingBoxMatrix );
}