Files
UnrealEngine/Engine/Source/Developer/MeshSimplifier/Private/Quadric.cpp
2025-05-18 13:04:45 +08:00

1084 lines
20 KiB
C++

// Copyright (C) 2009 Nine Realms, Inc
#include "Quadric.h"
#include "MatrixUtil.h"
DEFINE_LOG_CATEGORY_STATIC( LogQuadric, Log, All );
#if defined(_MSC_VER) && !defined(__clang__)
#pragma float_control( precise, on, push )
#pragma warning(disable:6011)
#endif
FEdgeQuadric::FEdgeQuadric( const QVec3 p0, const QVec3 p1, const float Weight )
{
n = p1 - p0;
const QScalar Length = sqrt( n | n );
if( Length < (QScalar)SMALL_NUMBER )
{
Zero();
return;
}
else
{
n.x /= Length;
n.y /= Length;
n.z /= Length;
}
a = Weight * Length;
nxx = a - a * n.x * n.x;
nyy = a - a * n.y * n.y;
nzz = a - a * n.z * n.z;
nxy = -a * n.x * n.y;
nxz = -a * n.x * n.z;
nyz = -a * n.y * n.z;
}
FQuadric::FQuadric( const QVec3 p0, const QVec3 p1, const QVec3 p2 )
{
const QVec3 p01 = p1 - p0;
const QVec3 p02 = p2 - p0;
// Compute the wedge product, giving the normal direction scaled by
// twice the triangle area.
QVec3 n = p02 ^ p01;
const QScalar Length = sqrt( n | n );
const QScalar area = 0.5 * Length;
if( Length < (QScalar)SMALL_NUMBER )
{
Zero();
return;
}
else
{
n.x /= Length;
n.y /= Length;
n.z /= Length;
}
nxx = n.x * n.x;
nyy = n.y * n.y;
nzz = n.z * n.z;
nxy = n.x * n.y;
nxz = n.x * n.z;
nyz = n.y * n.z;
const QScalar dist = -( n | p0 );
dn = dist * n;
d2 = dist * dist;
#if WEIGHT_BY_AREA
nxx *= area;
nyy *= area;
nzz *= area;
nxy *= area;
nxz *= area;
nyz *= area;
dn.x *= area;
dn.y *= area;
dn.z *= area;
d2 *= area;
a = area;
#else
a = 1.0;
#endif
}
FQuadric::FQuadric( const QVec3 p )
{
// (v - p)^T (v - p)
// v^T I v - 2 p^T v + p^T p
nxx = 1.0;
nyy = 1.0;
nzz = 1.0;
nxy = 0.0;
nxz = 0.0;
nyz = 0.0;
dn = -p;
d2 = p | p;
a = 0.0;
}
FQuadric::FQuadric( const QVec3 n, const QVec3 p )
{
// nn^T = projection matrix
//( v - nn^T v )^T ( v - nn^T v )
// v^T ( I - nn^T ) v - 2p^T ( I - nn^T ) v + (p^T p - p^T nn^T p)
nxx = 1.0 - n.x * n.x;
nyy = 1.0 - n.y * n.y;
nzz = 1.0 - n.z * n.z;
nxy = -n.x * n.y;
nxz = -n.x * n.z;
nyz = -n.y * n.z;
const QScalar dist = -( n | p );
dn = -p - dist * n;
d2 = (p | p) - dist * dist;
a = 0.0;
}
float FQuadric::Evaluate( const FVector3f& Point ) const
{
// Q(v) = vt*A*v + 2*bt*v + c
// v = [ p ]
// [ s ]
// A = [ C B ]
// [ Bt aI ]
// C = n*nt
// B = -g[ 0 .. m ]
// b = [ dn ]
// [ -d[ 0 .. m] ]
// c = d2
QVec3 p = Point;
// A*v = [ C*p + B*s ]
// [ Bt*p + a*s ]
// C*p
QScalar x = p | QVec3( nxx, nxy, nxz );
QScalar y = p | QVec3( nxy, nyy, nyz );
QScalar z = p | QVec3( nxz, nyz, nzz );
// vt*A*v = pt * ( C*p + B*s ) + st * ( Bt*p + a*s )
// pt * (C*p + B*s)
QScalar vAv = p | QVec3( x, y, z );
// bt*v
QScalar btv = p | dn;
// Q(v) = vt*A*v + 2*bt*v + c
QScalar Q = vAv + 2.0 * btv + d2;
if( Q < 0.0 || !FMath::IsFinite( Q ) )
{
Q = 0.0;
}
return Q;
}
FQuadricAttr::FQuadricAttr(
const QVec3 p0, const QVec3 p1, const QVec3 p2,
const float* attr0, const float* attr1, const float* attr2,
const float* AttributeWeights, uint32 NumAttributes )
{
const QVec3 p01 = p1 - p0;
const QVec3 p02 = p2 - p0;
// Compute the wedge product, giving the normal direction scaled by
// twice the triangle area.
QVec3 n = p02 ^ p01;
#if VOLUME_CONSTRAINT
// Already scaled by area*2
nv = n;
dv = -( n | p0 );
#endif
const QScalar Length = sqrt( n | n );
const QScalar area = 0.5 * Length;
//if (Length < QScalar(SMALL_NUMBER))
if( area < 1e-12 )
{
Zero( NumAttributes );
return;
}
else
{
n.x /= Length;
n.y /= Length;
n.z /= Length;
}
nxx = n.x * n.x;
nyy = n.y * n.y;
nzz = n.z * n.z;
nxy = n.x * n.y;
nxz = n.x * n.z;
nyz = n.y * n.z;
const QScalar dist = -( n | p0 );
dn = dist * n;
d2 = dist * dist;
// solve for g
// (p1 - p0) | g = a1 - a0
// (p2 - p0) | g = a2 - a0
// n | g = 0
QScalar A[] =
{
p01.x, p01.y, p01.z,
p02.x, p02.y, p02.z,
n.x, n.y, n.z
};
uint32 Pivot[3];
bool bInvertable = LUPFactorize( A, Pivot, 3, (QScalar)1e-12 );
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
if( AttributeWeights[i] == 0.0f )
{
g[i].x = 0.0;
g[i].y = 0.0;
g[i].z = 0.0;
d[i] = 0.0;
continue;
}
float a0 = AttributeWeights[i] * attr0[i];
float a1 = AttributeWeights[i] * attr1[i];
float a2 = AttributeWeights[i] * attr2[i];
a0 = FMath::IsFinite( a0 ) ? a0 : 0.0f;
a1 = FMath::IsFinite( a1 ) ? a1 : 0.0f;
a2 = FMath::IsFinite( a2 ) ? a2 : 0.0f;
QVec3 Grad;
if( !bInvertable )
{
Grad.x = 0.0;
Grad.y = 0.0;
Grad.z = 0.0;
}
else
{
QScalar b[] =
{
a1 - a0,
a2 - a0,
0.0
};
LUPSolve( A, Pivot, 3, b, (QScalar*)&Grad );
// Newton's method iterative refinement.
{
QScalar Residual[] =
{
b[0] - ( Grad | p01 ),
b[1] - ( Grad | p02 ),
b[2] - ( Grad | n )
};
TVec3< QScalar > Error;
LUPSolve( A, Pivot, 3, Residual, (QScalar*)&Error );
Grad = Grad + Error;
}
}
g[i] = Grad;
// p0 | g + d = a0
d[i] = a0 - ( g[i] | p0 );
nxx += g[i].x * g[i].x;
nyy += g[i].y * g[i].y;
nzz += g[i].z * g[i].z;
nxy += g[i].x * g[i].y;
nxz += g[i].x * g[i].z;
nyz += g[i].y * g[i].z;
dn += d[i] * g[i];
d2 += d[i] * d[i];
}
#if WEIGHT_BY_AREA
nxx *= area;
nyy *= area;
nzz *= area;
nxy *= area;
nxz *= area;
nyz *= area;
dn.x *= area;
dn.y *= area;
dn.z *= area;
d2 *= area;
for( uint32 i = 0; i < NumAttributes; i++ )
{
g[i].x *= area;
g[i].y *= area;
g[i].z *= area;
d[i] *= area;
}
a = area;
#else
a = 1.0;
#endif
}
void FQuadricAttr::Rebase(
const FVector3f& RESTRICT Point,
const float* RESTRICT Attribute,
const float* RESTRICT AttributeWeights,
uint32 NumAttributes )
{
//if( a < (QScalar)SMALL_NUMBER )
if( a < 1e-12 )
return;
const QVec3 p0( Point );
// Already scaled by area*2
const QScalar InvA = 1.0 / a;
const QScalar Dist2A = -( nv | p0 );
const QScalar DistHalf = 0.25 * Dist2A * InvA;
dn = DistHalf * nv;
d2 = DistHalf * Dist2A;
dv = Dist2A;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
if( AttributeWeights[i] == 0.0f )
continue;
float a0 = AttributeWeights[i] * Attribute[i];
checkSlow( FMath::IsFinite( a0 ) );
// p0 | g + d = a0
const QScalar qd = a0 - ( g[i] | p0 ) * InvA;
d[i] = qd * a;
dn += qd * g[i];
d2 += qd * d[i];
}
}
void FQuadricAttr::Add(
const FQuadricAttr& RESTRICT q,
const FVector3f& RESTRICT Point,
const float* RESTRICT Attribute,
const float* RESTRICT AttributeWeights,
uint32 NumAttributes )
{
//if( q.a < (QScalar)SMALL_NUMBER )
if( q.a < 1e-12 )
return;
nxx += q.nxx;
nyy += q.nyy;
nzz += q.nzz;
nxy += q.nxy;
nxz += q.nxz;
nyz += q.nyz;
const QVec3 p0( Point );
// Already scaled by area*2
const QScalar InvA = 1.0 / q.a;
const QScalar Dist2A = -( q.nv | p0 );
const QScalar DistHalf = 0.25 * Dist2A * InvA;
dn += DistHalf * q.nv;
d2 += DistHalf * Dist2A;
nv += q.nv;
dv += Dist2A;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
QVec3* RESTRICT qg = (QVec3*)( &q + 1 );
for( uint32 i = 0; i < NumAttributes; i++ )
{
if( AttributeWeights[i] == 0.0f )
continue;
float a0 = AttributeWeights[i] * Attribute[i];
checkSlow( FMath::IsFinite( a0 ) );
// p0 | g + d = a0
const QScalar qd = a0 - ( qg[i] | p0 ) * InvA;
const QScalar qda = qd * q.a;
g[i] += qg[i];
d[i] += qda;
dn += qd * qg[i];
d2 += qd * qda;
}
a += q.a;
}
void FQuadricAttr::Add(
const FQuadricAttr& RESTRICT q,
uint32 NumAttributes )
{
nxx += q.nxx;
nyy += q.nyy;
nzz += q.nzz;
nxy += q.nxy;
nxz += q.nxz;
nyz += q.nyz;
dn += q.dn;
d2 += q.d2;
nv += q.nv;
dv += q.dv;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
QVec3* RESTRICT qg = (QVec3*)( &q + 1 );
QScalar* RESTRICT qd = (QScalar*)( qg + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
g[i] += qg[i];
d[i] += qd[i];
}
a += q.a;
}
void FQuadricAttr::Zero( uint32 NumAttributes )
{
nxx = 0.0;
nyy = 0.0;
nzz = 0.0;
nxy = 0.0;
nxz = 0.0;
nyz = 0.0;
dn = 0.0;
d2 = 0.0;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
g[i] = 0.0;
d[i] = 0.0;
}
a = 0.0;
#if VOLUME_CONSTRAINT
nv = 0.0;
dv = 0.0;
#endif
}
float FQuadricAttr::Evaluate( const FVector3f& RESTRICT Point, const float* RESTRICT Attributes, const float* RESTRICT AttributeWeights, uint32 NumAttributes ) const
{
// Q(v) = vt*A*v + 2*bt*v + c
// v = [ p ]
// [ s ]
// A = [ C B ]
// [ Bt aI ]
// C = n*nt
// B = -g[ 0 .. m ]
// b = [ dn ]
// [ -d[ 0 .. m] ]
// c = d2
QVec3 p = Point;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
// A*v = [ C*p + B*s ]
// [ Bt*p + a*s ]
// C*p
QScalar x = p | QVec3( nxx, nxy, nxz );
QScalar y = p | QVec3( nxy, nyy, nyz );
QScalar z = p | QVec3( nxz, nyz, nzz );
#if 0
QScalar* RESTRICT s = (QScalar*)FMemory_Alloca( NumAttributes * sizeof( QScalar ) );
for( uint32 i = 0; i < NumAttributes; i++ )
{
s[i] = AttributeWeights[i] * Attributes[i];
}
// B*s
for( uint32 i = 0; i < NumAttributes; i++ )
{
x -= g[i].x * s[i];
y -= g[i].y * s[i];
z -= g[i].z * s[i];
}
// vt*A*v = pt * ( C*p + B*s ) + st * ( Bt*p + a*s )
// pt * (C*p + B*s)
QScalar vAv = p | QVec3( x, y, z );
// st * ( Bt*p + a*s )
for( uint32 i = 0; i < NumAttributes; i++ )
{
vAv += s[i] * ( a * s[i] - ( p | g[i] ) );
}
// bt*v
QScalar btv = p | dn;
for( uint32 i = 0; i < NumAttributes; i++ )
{
btv -= d[i] * s[i];
}
// Q(v) = vt*A*v + 2*bt*v + c
QScalar Q = vAv + 2.0 * btv + d2;
#else
// Q(v) = vt*A*v + 2*bt*v + c
QScalar Q = ( p | QVec3( x, y, z ) ) + 2.0 * ( p | dn ) + d2;
for( uint32 i = 0; i < NumAttributes; i++ )
{
QScalar pgd = (p | g[i]) + d[i];
QScalar s = AttributeWeights[i] * Attributes[i];
// st * ( Bt*p + a*s + B + b )
Q += s * ( a * s - 2.0 * pgd );
}
#endif
if( Q < 0.0 || !FMath::IsFinite( Q ) )
{
Q = 0.0;
}
return Q;
}
float FQuadricAttr::CalcAttributesAndEvaluate( const FVector3f& RESTRICT Point, float* RESTRICT Attributes, const float* RESTRICT AttributeWeights, uint32 NumAttributes ) const
{
// Q(v) = vt*A*v + 2*bt*v + c
// v = [ p ]
// [ s ]
// A = [ C B ]
// [ Bt aI ]
// C = n*nt
// B = -g[ 0 .. m ]
// b = [ dn ]
// [ -d[ 0 .. m] ]
// c = d2
QVec3 p = Point;
// A*v = [ C*p + B*s ]
// [ Bt*p + a*s ]
#if 0
// C*p + 2*bt*p
QScalar x = ( p | QVec3( nxx, nxy, nxz ) ) + 2.0 * dn.x;
QScalar y = ( p | QVec3( nxy, nyy, nyz ) ) + 2.0 * dn.y;
QScalar z = ( p | QVec3( nxz, nyz, nzz ) ) + 2.0 * dn.z;
QScalar w = 0.0;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
if( AttributeWeights[i] != 0.0f )
{
QScalar s = ( (p | g[i]) + d[i] ) / a;
Attributes[i] = s / AttributeWeights[i];
// Many things cancel when s is the above.
// s * ( a * s - g[i][0] * px - g[i][1] * py - g[i][2] * pz ) - 2.0*d[i]*s == -d[i] * s
// B*s + b*s
x -= g[i].x * s;
y -= g[i].y * s;
z -= g[i].z * s;
w -= d[i] * s;
}
}
// vt*A*v = pt * ( C*p + B*s ) + st * ( Bt*p + a*s )
QScalar vAv_2btv = ( p | QVec3( x, y, z ) ) + w;
// Q(v) = vt*A*v + 2*bt*v + c
QScalar Q = vAv_2btv + d2;
#else
// C*p
QScalar x = p | QVec3( nxx, nxy, nxz );
QScalar y = p | QVec3( nxy, nyy, nyz );
QScalar z = p | QVec3( nxz, nyz, nzz );
// Q(v) = vt*A*v + 2*bt*v + c
QScalar Q = ( p | QVec3( x, y, z ) ) + 2.0 * ( p | dn ) + d2;
QVec3* RESTRICT g = (QVec3*)( this + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
if( AttributeWeights[i] != 0.0f )
{
QScalar pgd = (p | g[i]) + d[i];
QScalar s = pgd / a;
Attributes[i] = s / AttributeWeights[i];
// Many things cancel when s is the above.
// s * ( a * s - g[i][0] * px - g[i][1] * py - g[i][2] * pz ) - 2.0*d[i]*s == -d[i] * s
// B*s + b*s
Q -= pgd * s;
}
}
#endif
if( Q < 0.0 || !FMath::IsFinite( Q ) )
{
Q = 0.0;
}
return Q;
}
bool FQuadricAttrOptimizer::Optimize( FVector3f& Position ) const
{
// A * v = -b
// v = [ p ]
// [ s ]
// A = [ C B ]
// [ Bt aI ]
// C = n*nt
// B = -g[ 0 .. m ]
// b = [ dn ]
// [ -d[ 0 .. m] ]
// ( C - 1/a * B*Bt ) * p = -1/a * B*d - dn
if( a < 1e-12 )
{
return false;
}
QScalar InvA = 1.0 / a;
// M = C - 1/a * B*Bt
QScalar Mxx = nxx - BBtxx * InvA;
QScalar Myy = nyy - BBtyy * InvA;
QScalar Mzz = nzz - BBtzz * InvA;
QScalar Mxy = nxy - BBtxy * InvA;
QScalar Mxz = nxz - BBtxz * InvA;
QScalar Myz = nyz - BBtyz * InvA;
// -1/a * B*d - dn
QVec3 aBddn = Bd * InvA - dn;
/*
float3x3 M =
{
Mxx, Mxy, Mxz,
Mxy, Myy, Myz,
Mxz, Myz, Mzz
};
float3 b = { aBddnx, aBddny, aBddnz };
p = Inverse(M) * b;
*/
QScalar M[] =
{
Mxx, Mxy, Mxz,
Mxy, Myy, Myz,
Mxz, Myz, Mzz
};
QScalar b[] = { aBddn.x, aBddn.y, aBddn.z };
#if PSEUDO_INVERSE
QScalar A[9];
QScalar V[9];
QScalar S[3];
FMemory::Memcpy( A, M );
JacobiSVD::EigenSolver3( A, S, V, (QScalar)SMALL_NUMBER );
PseudoInverse( S, 3, 1e-6 );
// Rebase
for( int i = 0; i < 3; i++ )
for( int j = 0; j < 3; j++ )
b[i] -= M[ 3*i + j ] * Position[j];
QScalar x[3];
PseudoSolve( V, S, 3, b, x );
//if( PseudoSolveIterate( M, V, S, 3, b, x ) )
{
Position.X += x[0];
Position.Y += x[1];
Position.Z += x[2];
return true;
}
#else
uint32 Pivot[3];
QScalar LU[9];
FMemory::Memcpy( LU, M );
if( LUPFactorize( LU, Pivot, 3, (QScalar)1e-12 ) )
{
QScalar p[3];
if( LUPSolveIterate( M, LU, Pivot, 3, b, p ) )
{
Position.X = p[0];
Position.Y = p[1];
Position.Z = p[2];
return true;
}
}
#endif
return false;
}
bool FQuadricAttrOptimizer::OptimizeVolume( FVector3f& Position ) const
{
// A * v = -b
// v = [ p ]
// [ s ]
// A = [ C B ]
// [ Bt aI ]
// C = n*nt
// B = -g[ 0 .. m ]
// b = [ dn ]
// [ -d[ 0 .. m] ]
// ( C - 1/a * B*Bt ) * p = -1/a * B*d - dn
if( a < 1e-12 )
{
return false;
}
QScalar InvA = 1.0 / a;
// M = C - 1/a * B*Bt
QScalar Mxx = nxx - BBtxx * InvA;
QScalar Myy = nyy - BBtyy * InvA;
QScalar Mzz = nzz - BBtzz * InvA;
QScalar Mxy = nxy - BBtxy * InvA;
QScalar Mxz = nxz - BBtxz * InvA;
QScalar Myz = nyz - BBtyz * InvA;
// -1/a * B*d - dn
QVec3 aBddn = Bd * InvA - dn;
#if VOLUME_CONSTRAINT
// Only use the volume constraint if it is well conditioned
if( (nv | nv) > 1e-12 )
{
const QScalar M[] =
{
Mxx, Mxy, Mxz, nv.x,
Mxy, Myy, Myz, nv.y,
Mxz, Myz, Mzz, nv.z,
nv.x, nv.y, nv.z, 0.0
};
QScalar b[] = { aBddn.x, aBddn.y, aBddn.z, -dv };
#if PSEUDO_INVERSE
QScalar A[16];
QScalar V[16];
QScalar S[4];
FMemory::Memcpy( A, M );
JacobiSVD::EigenSolver4( A, S, V, (QScalar)SMALL_NUMBER );
PseudoInverse( S, 4, 1e-6 );
// Rebase
for( int i = 0; i < 4; i++ )
for( int j = 0; j < 3; j++ )
b[i] -= M[ 4*i + j ] * Position[j];
// Guess for the Lagrange multiplier
#if 1
if( (nv | nv) > 1e-4 )
{
/*
Guessing 0 for position (already rebased)
M*0 + lm*nv = b
nv * lm = b
Solved with least squares (same as projection)
A*x = b
x = (A^T * A)^-1 * A^T * b
lm = (nv^T * nv)^-1 * nv^T*b
lm = (nv | b ) / (nv | nv);
*/
QScalar lm = ( nv.x * b[0] + nv.y * b[1] + nv.z * b[2] ) / ( nv | nv );
// Rebase Lagrange multiplier
for( int i = 0; i < 4; i++ )
b[i] -= M[ 4*i + 3 ] * lm;
}
#endif
// Newton iterate Lagrange guess
QScalar x[4];
for( uint32 k = 0; k < 4; k++ )
{
PseudoSolve( V, S, 4, b, x );
// Rebase Lagrange multiplier
for( int i = 0; i < 4; i++ )
b[i] -= M[ 4*i + 3 ] * x[3];
}
PseudoSolve( V, S, 4, b, x );
//if( PseudoSolveIterate( M, V, S, 4, b, x ) )
{
Position.X += x[0];
Position.Y += x[1];
Position.Z += x[2];
return true;
}
#else
uint32 Pivot[4];
QScalar LU[16];
FMemory::Memcpy( LU, M );
if( LUPFactorize( LU, Pivot, 4, (QScalar)1e-12 ) )
{
QScalar p[4];
if( LUPSolveIterate( M, LU, Pivot, 4, b, p ) )
{
Position.X = p[0];
Position.Y = p[1];
Position.Z = p[2];
return true;
}
}
#endif
}
#endif
return false;
}
bool FQuadricAttrOptimizer::OptimizeLinear( const FVector3f& Position0, const FVector3f& Position1, FVector3f& Position ) const
{
// Optimize on a line instead of full 3D.
// A * v = -b
// v = [ p ]
// [ s ]
// A = [ C B ]
// [ Bt aI ]
// C = n*nt
// B = -g[ 0 .. m ]
// b = [ dn ]
// [ -d[ 0 .. m] ]
// ( C - 1/a * B*Bt ) * p = -1/a * B*d - dn
if( a < 1e-12 )
{
return false;
}
QScalar InvA = 1.0 / a;
// M = C - 1/a * B*Bt
QScalar Mxx = nxx - BBtxx * InvA;
QScalar Myy = nyy - BBtyy * InvA;
QScalar Mzz = nzz - BBtzz * InvA;
QScalar Mxy = nxy - BBtxy * InvA;
QScalar Mxz = nxz - BBtxz * InvA;
QScalar Myz = nyz - BBtyz * InvA;
// -1/a * B*d - dn
QVec3 aBddn = Bd * InvA - dn;
QVec3 p0( Position0 );
QVec3 p1( Position1 );
// M*p0
QVec3 m0(
p0.x * Mxx + p0.y * Mxy + p0.z * Mxz,
p0.x * Mxy + p0.y * Myy + p0.z * Myz,
p0.x * Mxz + p0.y * Myz + p0.z * Mzz
);
// M*p1
QVec3 m1(
p1.x * Mxx + p1.y * Mxy + p1.z * Mxz,
p1.x * Mxy + p1.y * Myy + p1.z * Myz,
p1.x * Mxz + p1.y * Myz + p1.z * Mzz
);
// M*p1 - M*p0
QVec3 m01 = m1 - m0;
/*
float3x3 M =
{
Mxx, Mxy, Mxz,
Mxy, Myy, Myz,
Mxz, Myz, Mzz
};
float3 b = { aBddnx, aBddny, aBddnz };
M * p = b
M*( p0 + t*(p1 - p0) ) = b
(M*p1 - M*p0) * t = b - M*p0
m01 * t = b - m0
Solved with least squares
A*x = b
x = (A^T * A)^-1 * A^T * b
t = (m01^T * m01)^-1 * m01^T * (b - m0)
t = ( m01 | (b - m0) ) / (m01 | m01)
*/
QScalar m01Sqr = m01 | m01;
if( m01Sqr < 1e-16 )
{
return false;
}
QVec3 bm0 = aBddn - m0;
QScalar t = (m01 | bm0) / m01Sqr;
#if VOLUME_CONSTRAINT
QScalar nvSqr = nv | nv;
// Only use the volume constraint if it is well conditioned
if( nvSqr > 1e-12 )
{
/*
* If Volume Preservation is desired, a scalar Lagrange multiplier 'lm' is used to inflate the system
*
* ( M, nv ) ( p ) = ( b )
* ( nv^T, 0 ) ( lm ) ( -dv )
*
M * p + lm * nv = b
nv^T * p = -dv
M*( p0 + t*(p1 - p0) ) + lm*nv = b
(M*p1 - M*p0) * t + nv * lm = b - M*p0
(nv | p1 - nv | p0) * t = -dv - (nv | p0)
[ M * (p1 - p0), nv ] [ t ] = [ b - M * p0 ]
[ nv | (p1 - p0), 0 ] [ lm ] [ -dv - nv | p0 ]
[ m01, nv ] [ t ] = [ b - m0 ]
[ nv01, 0 ] [ lm ] [ -dv - nv0 ]
Solved with least squares
A*x = b
x = (A^T * A)^-1 * A^T * b
*/
QScalar nv0 = nv | p0;
QScalar nv01 = (nv | p1) - nv0;
// A^T * A =
// [ m01 | m01 + nv01 | nv01, m01 | nv ]
// [ m01 | nv, nv | nv ]
QScalar ATAxx = m01Sqr + nv01 * nv01;
QScalar ATAxy = m01 | nv;
QScalar ATAyy = nvSqr;
QScalar det = ATAxx * ATAyy - ATAxy * ATAxy;
if( FMath::Abs( det ) > 1e-16 )
{
// (A^T * A)^-1
QScalar iATAxx = ATAyy;
QScalar iATAxy = -ATAxy;
QScalar iATAyy = ATAxx;
// A^T * b
// [ m01 | (b - m0) - (dv + nv0) * nv01 ]
// [ nv | (b - m0) ]
QScalar ATb[] =
{
(m01 | bm0) - (dv + nv0) * nv01,
(nv | bm0)
};
t = ( iATAxx * ATb[0] + iATAxy * ATb[1] ) / det;
}
}
#endif
t = FMath::Clamp< QScalar >( t, 0.0, 1.0 );
QVec3 p = p0 * (1.0 - t) + p1 * t;
Position.X = p.x;
Position.Y = p.y;
Position.Z = p.z;
return true;
}
#if defined(_MSC_VER) && !defined(__clang__)
#pragma float_control( pop )
#endif