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

580 lines
11 KiB
C++

// Copyright (C) 2009 Nine Realms, Inc
//
#pragma once
#include "CoreMinimal.h"
// [ Hoppe 1999, "New Quadric Metric for Simplifying Meshes with Appearance Attributes" ]
// [ Hoppe 2000, "Efficient minimization of new quadric metric for simplifying meshes with appearance attributes" ]
// doubles needed for precision
#if defined(_MSC_VER) && !defined(__clang__)
#pragma float_control( precise, on, push )
#pragma warning(disable:6011)
#endif
#define WEIGHT_BY_AREA 1
#define VOLUME_CONSTRAINT 1
#define USE_FMA 0
#define PSEUDO_INVERSE 0
#if USE_FMA
// Kahan's algorithm
template< typename T >
inline T DifferenceOfProducts( T a, T b, T c, T d )
{
T cd = c * d;
T Error = std::fma( -c, d, cd );
T ab_cd = std::fma( a, b, -cd );
return ab_cd + Error;
}
// Knuth 1974
template< typename T >
T TwoSumError( T a, T b )
{
T x = a + b;
T z = x - b;
return ( a - (x - z) ) + (b - z);
}
template< typename T >
T TwoProdError( T a, T b )
{
T x = a * b;
return std::fma( a, b, -x );
}
#endif
template< typename T >
struct TVec3
{
T x;
T y;
T z;
TVec3() = default;
TVec3( T Scalar )
: x( Scalar ), y( Scalar ), z( Scalar )
{}
TVec3( T InX, T InY, T InZ )
: x( InX ), y( InY ), z( InZ )
{}
TVec3( const FVector3f& v )
: x( v.X ), y( v.Y ), z( v.Z )
{}
TVec3 operator-() const
{
return TVec3<T>( -x, -y, -z );
}
TVec3 operator*( T Scalar ) const
{
return TVec3<T>(
x * Scalar,
y * Scalar,
z * Scalar );
}
TVec3 operator+( const TVec3& v ) const
{
return TVec3<T>(
x + v.x,
y + v.y,
z + v.z );
}
TVec3 operator-( const TVec3& v ) const
{
return TVec3<T>(
x - v.x,
y - v.y,
z - v.z );
}
TVec3& operator+=( const TVec3& v )
{
x += v.x;
y += v.y;
z += v.z;
return *this;
}
T operator|( const TVec3& v ) const
{
#if USE_FMA
/*
T zz = z * v.z;
T Error = std::fma( z, v.z, -zz );
T yy_zz = std::fma( y, v.y, zz );
T xx_yy_zz = std::fma( x, v.z, yy_zz );
return xx_yy_zz + Error;
*/
T xx = x * v.x;
T yy = y * v.y;
T zz = z * v.z;
T Dot, DotError;
DotError = TwoProdError( x, v.x );
Dot = xx;
DotError += TwoProdError( y, v.y ) + TwoSumError( Dot, yy );
Dot += yy;
DotError += TwoProdError( z, v.z ) + TwoSumError( Dot, zz );
Dot += zz;
return Dot + DotError;
#else
return x * v.x + y * v.y + z * v.z;
#endif
}
TVec3 operator^( const TVec3& v ) const
{
TVec3 Result;
#if USE_FMA
Result.x = DifferenceOfProducts( y, v.z, z, v.y );
Result.y = DifferenceOfProducts( z, v.x, x, v.z );
Result.z = DifferenceOfProducts( x, v.y, y, v.x );
#else
Result.x = y * v.z - z * v.y;
Result.y = z * v.x - x * v.z;
Result.z = x * v.y - y * v.x;
#endif
return Result;
}
};
template< typename T >
FORCEINLINE TVec3<T> operator*( T Scalar, const TVec3<T>& v )
{
return v.operator*( Scalar );
}
using QScalar = double;
using QVec3 = TVec3< QScalar >;
class FEdgeQuadric
{
public:
FEdgeQuadric() {}
FEdgeQuadric( const QVec3 p0, const QVec3 p1, const float Weight );
FEdgeQuadric( const QVec3 p0, const QVec3 p1, const QVec3 FaceNormal, const float Weight );
void Zero();
QScalar nxx;
QScalar nyy;
QScalar nzz;
QScalar nxy;
QScalar nxz;
QScalar nyz;
QVec3 n;
QScalar a;
};
inline FEdgeQuadric::FEdgeQuadric( const QVec3 p0, const QVec3 p1, const QVec3 FaceNormal, const float Weight )
{
const QVec3 p01 = p1 - p0;
n = p01 ^ FaceNormal;
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 * sqrt( p01 | p01 );
nxx = a * n.x * n.x;
nyy = a * n.y * n.y;
nzz = a * n.z * n.z;
nxy = a * n.x * n.y;
nxz = a * n.x * n.z;
nyz = a * n.y * n.z;
}
inline void FEdgeQuadric::Zero()
{
nxx = 0.0;
nyy = 0.0;
nzz = 0.0;
nxy = 0.0;
nxz = 0.0;
nyz = 0.0;
n = 0.0;
a = 0.0;
}
// Error quadric for position only
class FQuadric
{
public:
FQuadric() {}
// Distance from point
FQuadric( const QVec3 p );
// Distance from line, n must be normalized
FQuadric( const QVec3 n, const QVec3 p );
// Distance from triangle
FQuadric( const QVec3 p0, const QVec3 p1, const QVec3 p2 );
void Zero();
FQuadric& operator+=( const FQuadric& q );
void Add( const FEdgeQuadric& q, const FVector3f& Point );
// Evaluate error at point
float Evaluate( const FVector3f& p ) const;
QScalar nxx;
QScalar nyy;
QScalar nzz;
QScalar nxy;
QScalar nxz;
QScalar nyz;
QVec3 dn;
QScalar d2;
QScalar a;
};
inline void FQuadric::Zero()
{
nxx = 0.0;
nyy = 0.0;
nzz = 0.0;
nxy = 0.0;
nxz = 0.0;
nyz = 0.0;
dn = 0.0;
d2 = 0.0;
a = 0.0;
}
inline FQuadric& FQuadric::operator+=( const FQuadric& q )
{
nxx += q.nxx;
nyy += q.nyy;
nzz += q.nzz;
nxy += q.nxy;
nxz += q.nxz;
nyz += q.nyz;
dn += q.dn;
d2 += q.d2;
a += q.a;
return *this;
}
inline void FQuadric::Add( const FEdgeQuadric& RESTRICT EdgeQuadric, const FVector3f& Point )
{
const QVec3 p0( Point );
const QScalar Dist = -( EdgeQuadric.n | p0 );
nxx += EdgeQuadric.nxx;
nyy += EdgeQuadric.nyy;
nzz += EdgeQuadric.nzz;
nxy += EdgeQuadric.nxy;
nxz += EdgeQuadric.nxz;
nyz += EdgeQuadric.nyz;
QScalar aDist = EdgeQuadric.a * Dist;
//dn += aDist * EdgeQuadric.n;
//d2 += aDist * Dist;
dn += EdgeQuadric.a * -p0 - aDist * EdgeQuadric.n;
d2 += EdgeQuadric.a * (p0 | p0) - aDist * Dist;
}
// Error quadric including attributes
// Attributes are assumed to be allocated immediately after this struct!
// See TQuadricAttr below for expected layout for a static case.
class FQuadricAttr : public FQuadric
{
public:
FQuadricAttr() {}
FQuadricAttr(
const QVec3 p0, const QVec3 p1, const QVec3 p2,
const float* a0, const float* a1, const float* a2,
const float* AttributeWeights, uint32 NumAttributes
);
void Rebase( const FVector3f& Point, const float* Attributes, const float* AttributeWeights, uint32 NumAttributes );
void Add( const FQuadricAttr& q, const FVector3f& Point, const float* Attribute, const float* AttributeWeights, uint32 NumAttributes );
void Add( const FQuadricAttr& q, uint32 NumAttributes );
void Zero( uint32 NumAttributes );
// Evaluate error at point with attributes and weights
float Evaluate( const FVector3f& Point, const float* Attributes, const float* AttributeWeights, uint32 NumAttributes ) const;
// Calculate attributes for point and evaluate error
float CalcAttributesAndEvaluate( const FVector3f& Point, float* Attributes, const float* AttributeWeights, uint32 NumAttributes ) const;
#if VOLUME_CONSTRAINT
QVec3 nv;
QScalar dv;
#endif
};
// Error quadric including attributes
// Static NumAttributes version of FQuadricAttr.
template< uint32 NumAttributes >
class TQuadricAttr : public FQuadricAttr
{
public:
TQuadricAttr() {}
TQuadricAttr(
const QVec3 p0, const QVec3 p1, const QVec3 p2,
const float* a0, const float* a1, const float* a2,
const float* AttributeWeights
);
void Rebase( const FVector3f& Point, const float* Attributes, const float* AttributeWeights );
void Add( const TQuadricAttr< NumAttributes >& q, const FVector3f& Point, const float* Attribute, const float* AttributeWeights );
void Zero();
TQuadricAttr< NumAttributes >& operator+=( const FQuadric& q );
TQuadricAttr< NumAttributes >& operator+=( const TQuadricAttr< NumAttributes >& q );
// Evaluate error at point with attributes and weights
float Evaluate( const FVector3f& Point, const float* Attributes, const float* AttributeWeights ) const;
// Calculate attributes for point and evaluate error
float CalcAttributesAndEvaluate( const FVector3f& Point, float* Attributes, const float* AttributeWeights ) const;
QScalar g[ NumAttributes ][3];
QScalar d[ NumAttributes ];
};
template< uint32 NumAttributes >
inline TQuadricAttr< NumAttributes >::TQuadricAttr(
const QVec3 p0, const QVec3 p1, const QVec3 p2,
const float* a0, const float* a1, const float* a2,
const float* AttributeWeights )
: FQuadricAttr(
p0, p1, p2,
a0, a1, a2,
AttributeWeights,
NumAttributes )
{}
template< uint32 NumAttributes >
inline void TQuadricAttr< NumAttributes >::Rebase(
const FVector3f& Point, const float* Attribute,
const float* AttributeWeights )
{
Rebase( Point, Attribute, AttributeWeights, NumAttributes );
}
template< uint32 NumAttributes >
inline void TQuadricAttr< NumAttributes >::Zero()
{
Zero( NumAttributes );
}
template< uint32 NumAttributes >
inline TQuadricAttr< NumAttributes >& TQuadricAttr< NumAttributes >::operator+=( const TQuadricAttr< NumAttributes >& RESTRICT q )
{
nxx += q.nxx;
nyy += q.nyy;
nzz += q.nzz;
nxy += q.nxy;
nxz += q.nxz;
nyz += q.nyz;
dn += q.dn;
d2 += q.d2;
for( uint32 i = 0; i < NumAttributes; i++ )
{
g[i][0] += q.g[i][0];
g[i][1] += q.g[i][1];
g[i][2] += q.g[i][2];
d[i] += q.d[i];
}
a += q.a;
#if VOLUME_CONSTRAINT
nv += q.nv;
dv += q.dv;
#endif
return *this;
}
template< uint32 NumAttributes >
inline float TQuadricAttr< NumAttributes >::CalcAttributesAndEvaluate( const FVector3f& Point, float* RESTRICT Attributes, const float* RESTRICT AttributeWeights ) const
{
return FQuadricAttr::CalcAttributesAndEvaluate( Point, Attributes, AttributeWeights, NumAttributes );
}
class FQuadricAttrOptimizer
{
public:
FQuadricAttrOptimizer();
void AddQuadric( const FQuadric& q );
void AddQuadric( const FQuadricAttr& q, uint32 NumAttributes );
// Find optimal point for minimal error
bool Optimize( FVector3f& Position ) const;
bool OptimizeVolume( FVector3f& Position ) const;
bool OptimizeLinear( const FVector3f& Position0, const FVector3f& Position1, FVector3f& Position ) const;
private:
QScalar nxx;
QScalar nyy;
QScalar nzz;
QScalar nxy;
QScalar nxz;
QScalar nyz;
QVec3 dn;
QScalar a;
#if VOLUME_CONSTRAINT
QVec3 nv;
QScalar dv;
#endif
QScalar BBtxx;
QScalar BBtyy;
QScalar BBtzz;
QScalar BBtxy;
QScalar BBtxz;
QScalar BBtyz;
QVec3 Bd;
};
inline FQuadricAttrOptimizer::FQuadricAttrOptimizer()
{
nxx = 0.0;
nyy = 0.0;
nzz = 0.0;
nxy = 0.0;
nxz = 0.0;
nyz = 0.0;
dn = 0.0;
a = 0.0;
#if VOLUME_CONSTRAINT
nv = 0.0;
dv = 0.0;
#endif
BBtxx = 0.0;
BBtyy = 0.0;
BBtzz = 0.0;
BBtxy = 0.0;
BBtxz = 0.0;
BBtyz = 0.0;
Bd = 0.0;
}
inline void FQuadricAttrOptimizer::AddQuadric( const FQuadric& RESTRICT q )
{
nxx += q.nxx;
nyy += q.nyy;
nzz += q.nzz;
nxy += q.nxy;
nxz += q.nxz;
nyz += q.nyz;
dn += q.dn;
//a += q.a;
}
inline void FQuadricAttrOptimizer::AddQuadric( const FQuadricAttr& RESTRICT q, uint32 NumAttributes )
{
if( q.a < (QScalar)SMALL_NUMBER )
return;
nxx += q.nxx;
nyy += q.nyy;
nzz += q.nzz;
nxy += q.nxy;
nxz += q.nxz;
nyz += q.nyz;
dn += q.dn;
a += q.a;
#if VOLUME_CONSTRAINT
nv += q.nv;
dv += q.dv;
#endif
QVec3* RESTRICT g = (QVec3*)( &q + 1 );
QScalar* RESTRICT d = (QScalar*)( g + NumAttributes );
for( uint32 i = 0; i < NumAttributes; i++ )
{
// B*Bt
BBtxx += g[i].x * g[i].x;
BBtyy += g[i].y * g[i].y;
BBtzz += g[i].z * g[i].z;
BBtxy += g[i].x * g[i].y;
BBtxz += g[i].x * g[i].z;
BBtyz += g[i].y * g[i].z;
// -B*d
Bd += g[i] * d[i];
}
}
#if defined(_MSC_VER) && !defined(__clang__)
#pragma float_control( pop )
#endif