628 lines
17 KiB
C++
628 lines
17 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "SkeletalSimplifierLinearAlgebra.h"
|
|
|
|
#include "MeshBuild.h" // ApprxEquals for normals & UVs
|
|
|
|
namespace SkeletalSimplifier
|
|
{
|
|
namespace VertexTypes
|
|
{
|
|
using namespace SkeletalSimplifier::LinearAlgebra;
|
|
|
|
/**
|
|
* Class that holds the dense vertex attributes.
|
|
* Normal, Tangent, BiTangent, Color, TextureCoords
|
|
*
|
|
* NB: this can be extended with additional float-type attributes.
|
|
*/
|
|
template <int32 NumTexCoords>
|
|
class TBasicVertexAttrs
|
|
{
|
|
public:
|
|
enum { NumUVs = NumTexCoords};
|
|
|
|
// NB: if you update the number of float-equivalent attributes, the '13' will have to be updated.
|
|
typedef TDenseVecD<13 + 2 * NumTexCoords> DenseVecDType;
|
|
|
|
typedef DenseVecDType WeightArrayType;
|
|
typedef TDenseBMatrix<13 + 2 * NumTexCoords> DenseBMatrixType;
|
|
|
|
// NB: Required that these all have float storage.
|
|
// - Base Attributes: size = 13 + 2 * NumTexCoord
|
|
|
|
FVector3f Normal; // 0, 1, 2
|
|
FVector3f Tangent; // 3, 4, 5
|
|
FVector3f BiTangent; // 6, 7, 8
|
|
FLinearColor Color; // 9, 10, 11, 12
|
|
FVector2f TexCoords[NumTexCoords]; // 13, .. 13 + NumTexCoords * 2 - 1
|
|
|
|
|
|
// used to manage identity of split/non-split vertex attributes.
|
|
// note, could have external storage for the above attributes and just use these to point to those values.
|
|
struct FElementIDs
|
|
{
|
|
int32 NormalID;
|
|
int32 TangentID;
|
|
int32 BiTangentID;
|
|
int32 ColorID;
|
|
int32 TexCoordsID[NumTexCoords];
|
|
|
|
enum { InvalidID = -1 };
|
|
|
|
// construct with invalid ID values.
|
|
FElementIDs()
|
|
{
|
|
NormalID = InvalidID;
|
|
TangentID = InvalidID;
|
|
BiTangentID = InvalidID;
|
|
ColorID = InvalidID;
|
|
for (int32 i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
TexCoordsID[i] = InvalidID;
|
|
}
|
|
}
|
|
|
|
FElementIDs(const FElementIDs& other)
|
|
{
|
|
NormalID = other.NormalID;
|
|
TangentID = other.TangentID;
|
|
BiTangentID = other.BiTangentID;
|
|
ColorID = other.ColorID;
|
|
for (int32 i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
TexCoordsID[i] = other.TexCoordsID[i];
|
|
}
|
|
}
|
|
|
|
// Subtract other ID struct from this one.
|
|
FElementIDs operator-(const FElementIDs& other) const
|
|
{
|
|
FElementIDs Result;
|
|
Result.NormalID = NormalID - other.NormalID;
|
|
Result.TangentID = TangentID - other.TangentID;
|
|
Result.BiTangentID = BiTangentID - other.BiTangentID;
|
|
Result.ColorID = ColorID - other.ColorID;
|
|
for (int i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
Result.TexCoordsID[i] = TexCoordsID[i] - other.TexCoordsID[i];
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
// copy other ID if the mask value is zero
|
|
void MaskedCopy(const FElementIDs& IDMask, const FElementIDs& other)
|
|
{
|
|
NormalID = (IDMask.NormalID == 0) ? other.NormalID : NormalID;
|
|
TangentID = (IDMask.TangentID == 0) ? other.TangentID : TangentID;
|
|
BiTangentID = (IDMask.BiTangentID == 0) ? other.BiTangentID : BiTangentID;
|
|
ColorID = (IDMask.ColorID == 0) ? other.ColorID : ColorID;
|
|
for (int i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
TexCoordsID[i] = (IDMask.TexCoordsID[i] == 0) ? other.TexCoordsID[i] : TexCoordsID[i];
|
|
}
|
|
}
|
|
|
|
// copy IDs values over to this for elemenets where IDMask == 0 and InverseIDMask != 0
|
|
void MaskedCopy(const FElementIDs& IDMask, const FElementIDs& InverseIDMask, const FElementIDs& BIDs)
|
|
{
|
|
if (InverseIDMask.NormalID != 0 && IDMask.NormalID == 0)
|
|
{
|
|
NormalID = BIDs.NormalID;
|
|
}
|
|
if (InverseIDMask.TangentID != 0 && IDMask.TangentID == 0)
|
|
{
|
|
TangentID = BIDs.TangentID;
|
|
}
|
|
if (InverseIDMask.BiTangentID != 0 && IDMask.BiTangentID == 0)
|
|
{
|
|
BiTangentID = BIDs.BiTangentID;
|
|
}
|
|
if (InverseIDMask.ColorID != 0 && IDMask.ColorID == 0)
|
|
{
|
|
ColorID = BIDs.ColorID;
|
|
}
|
|
for (int i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
if (InverseIDMask.TexCoordsID[i] != 0 && IDMask.TexCoordsID[i] == 0)
|
|
{
|
|
TexCoordsID[i] = BIDs.TexCoordsID[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
FElementIDs ElementIDs;
|
|
|
|
public:
|
|
// vector semantic wrapper for raw array
|
|
typedef TDenseArrayWrapper<float> DenseAttrAccessor;
|
|
|
|
|
|
/**
|
|
* default construct to zero values
|
|
*/
|
|
TBasicVertexAttrs() :
|
|
Normal(ForceInitToZero),
|
|
Tangent(ForceInitToZero),
|
|
BiTangent(ForceInitToZero),
|
|
Color(ForceInitToZero)
|
|
{
|
|
for (int32 i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
TexCoords[i] = FVector2f(ForceInitToZero);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* copy construct
|
|
*/
|
|
TBasicVertexAttrs(const TBasicVertexAttrs& Other) :
|
|
Normal(Other.Normal),
|
|
Tangent(Other.Tangent),
|
|
BiTangent(Other.BiTangent),
|
|
Color(Other.Color),
|
|
ElementIDs(Other.ElementIDs)
|
|
{
|
|
for (int32 i = 0; i < NumTexCoords; ++i)
|
|
{
|
|
TexCoords[i] = Other.TexCoords[i];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Number of float equivalents.
|
|
*/
|
|
static int32 Size() { return (sizeof(TBasicVertexAttrs) - sizeof(FElementIDs)) / sizeof(float); /*( return 13 + 2 * NumTexCoords;*/ }
|
|
|
|
/**
|
|
* Get access to the data as a generic linear array of floats.
|
|
*/
|
|
DenseAttrAccessor AsDenseAttrAccessor() { return DenseAttrAccessor((float*)&Normal, Size()); }
|
|
const DenseAttrAccessor AsDenseAttrAccessor() const { return DenseAttrAccessor((float*)&Normal, Size()); }
|
|
|
|
/**
|
|
* Assignment operator.
|
|
*/
|
|
TBasicVertexAttrs& operator=(const TBasicVertexAttrs& Other)
|
|
{
|
|
DenseAttrAccessor MyData = AsDenseAttrAccessor();
|
|
const DenseAttrAccessor OtherData = Other.AsDenseAttrAccessor();
|
|
|
|
const int32 NumElements = MyData.Num();
|
|
for (int32 i = 0; i < NumElements; ++i)
|
|
{
|
|
MyData[i] = OtherData[i];
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Method to insure that the attribute values are valid by correcting any invalid ones.
|
|
*/
|
|
void Correct()
|
|
{
|
|
Normal.Normalize();
|
|
Tangent -= FVector3f::DotProduct(Tangent, Normal) * Normal;
|
|
Tangent.Normalize();
|
|
BiTangent -= FVector3f::DotProduct(BiTangent, Normal) * Normal;
|
|
BiTangent -= FVector3f::DotProduct(BiTangent, Tangent) * Tangent;
|
|
BiTangent.Normalize();
|
|
Color = Color.GetClamped();
|
|
}
|
|
|
|
/**
|
|
* Strict equality test.
|
|
*/
|
|
bool operator==(const TBasicVertexAttrs& Other)
|
|
{
|
|
bool bIsEqual =
|
|
Tangent == Other.Tangent
|
|
&& BiTangent == Other.BiTangent
|
|
&& Normal == Other.Normal
|
|
&& Color == Other.Color;
|
|
|
|
for (int32 UVIndex = 0; bIsEqual && UVIndex < NumTexCoords; UVIndex++)
|
|
{
|
|
bIsEqual = bIsEqual && UVsEqual(TexCoords[UVIndex], Other.TexCoords[UVIndex]);
|
|
}
|
|
|
|
return bIsEqual;
|
|
}
|
|
|
|
/**
|
|
* Approx equality test.
|
|
*/
|
|
bool IsApproxEquals(const TBasicVertexAttrs& Other) const
|
|
{
|
|
bool bIsApprxEqual =
|
|
NormalsEqual(Tangent, Other.Tangent) &&
|
|
NormalsEqual(BiTangent, Other.BiTangent) &&
|
|
NormalsEqual(Normal, Other.Normal) &&
|
|
Color.Equals(Other.Color);
|
|
|
|
for (int32 UVIndex = 0; bIsApprxEqual && UVIndex < NumTexCoords; UVIndex++)
|
|
{
|
|
bIsApprxEqual = bIsApprxEqual && UVsEqual(TexCoords[UVIndex], Other.TexCoords[UVIndex]);
|
|
}
|
|
|
|
return bIsApprxEqual;
|
|
}
|
|
|
|
};
|
|
|
|
/** Dynamic vertex attributes. Distinguished from the BoneSparseVertexAttrs. Based on SparseVecD, rather than
|
|
* using dense vector wrappers, because of requirements by the simplifier.
|
|
*/
|
|
class DynamicVertexAttrs : public SparseVecD
|
|
{
|
|
public:
|
|
static void Correct()
|
|
{
|
|
// Do nothing.
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sparse attributes.
|
|
* Used to hold bone weights where the bone ID is the attribute key.
|
|
*/
|
|
class BoneSparseVertexAttrs : public SparseVecD
|
|
{
|
|
public:
|
|
|
|
#define SmallBoneSize 1.e-12
|
|
|
|
/**
|
|
* Deletes smallest bones if currently more than MaxBoneNumber, and maintain the normalization of weight (all sum to 1).
|
|
* keeping the bones sorted internally by weight (largest to smallest).
|
|
*/
|
|
void Correct( int32 MaxBoneNumber = 8)
|
|
{
|
|
if (!bIsEmpty())
|
|
{
|
|
|
|
DeleteSmallBones();
|
|
|
|
|
|
|
|
if (SparseData.Num() > MaxBoneNumber)
|
|
{
|
|
// sort by value from large to small
|
|
SparseData.ValueSort([](double A, double B)->bool { return B < A; });
|
|
|
|
SparseContainer Tmp;
|
|
int32 Count = 0;
|
|
for (const auto& BoneData : SparseData)
|
|
{
|
|
if (Count == MaxBoneNumber) break;
|
|
Tmp.Add(BoneData.Key, BoneData.Value);
|
|
Count++;
|
|
}
|
|
Swap(SparseData, Tmp);
|
|
|
|
}
|
|
|
|
SparseData.ValueSort([](double A, double B)->bool { return B < A; });
|
|
|
|
Normalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Note: the norm here is the sum of weights (not L2 or L1 norm)
|
|
*/
|
|
void Normalize()
|
|
{
|
|
double SumOfWeights = SumValues();
|
|
if (FMath::Abs(SumOfWeights) > 8 * SmallBoneSize)
|
|
{
|
|
double Inv = 1. / SumOfWeights;
|
|
|
|
operator*=(Inv);
|
|
}
|
|
else
|
|
{
|
|
Empty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes bones with very small weights. This may not be appropriate for all models.
|
|
*/
|
|
void DeleteSmallBones()
|
|
{
|
|
SparseContainer Tmp;
|
|
for (const auto& BoneData : SparseData)
|
|
{
|
|
if (BoneData.Value > SmallBoneSize ) Tmp.Add(BoneData.Key, BoneData.Value);
|
|
}
|
|
Swap(SparseData, Tmp);
|
|
}
|
|
|
|
/**
|
|
* Remove all bones.
|
|
*/
|
|
void Empty()
|
|
{
|
|
SparseContainer Tmp;
|
|
Swap(SparseData, Tmp);
|
|
}
|
|
|
|
/**
|
|
* Compare two sparse arrays. A small bone weight will be equivalent to no bone weight.
|
|
*/
|
|
bool IsApproxEquals(const BoneSparseVertexAttrs& Other, double Tolerance = (double)KINDA_SMALL_NUMBER) const
|
|
{
|
|
|
|
SparseVecD::SparseContainer Tmp = SparseData;
|
|
auto AddToElement = [&Tmp](const int32 j, const double Value)
|
|
{
|
|
double* Data = Tmp.Find(j);
|
|
if (Data != nullptr)
|
|
{
|
|
*Data += Value;
|
|
}
|
|
else
|
|
{
|
|
Tmp.Add(j, Value);
|
|
}
|
|
};
|
|
|
|
// Tmp = SparseData - Other.SparseData
|
|
for (const auto& Data : Other.SparseData)
|
|
{
|
|
AddToElement(Data.Key, -Data.Value);
|
|
}
|
|
|
|
bool bIsApproxEquals = true;
|
|
for (const auto& Data : Tmp)
|
|
{
|
|
bIsApproxEquals = bIsApproxEquals &&
|
|
(Data.Value < Tolerance) && (-Data.Value < Tolerance);
|
|
}
|
|
|
|
return bIsApproxEquals;
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Simplifier vertex type that has been extended to include additional sparse data
|
|
*
|
|
* Implements the interface needed by template simplifier code.
|
|
*/
|
|
|
|
template< typename BasicAttrContainerType_, typename AttrContainerType_ , typename BoneContainerType_>
|
|
class TSkeletalSimpVert
|
|
{
|
|
typedef TSkeletalSimpVert< BasicAttrContainerType_, AttrContainerType_, BoneContainerType_ > VertType;
|
|
|
|
public:
|
|
|
|
typedef BoneContainerType_ BoneContainer;
|
|
typedef AttrContainerType_ AttrContainerType;
|
|
typedef BasicAttrContainerType_ BasicAttrContainerType;
|
|
typedef typename BasicAttrContainerType::DenseVecDType DenseVecDType;
|
|
|
|
// - The "closest" vertex in the source mesh as measured by incremental edge collapse
|
|
// i.e. this number should be inherited from the closest vert during edge collapse.
|
|
// the value -1 will be used as a null-flag
|
|
|
|
int32 ClosestSrcVertIndex;
|
|
|
|
// The Vertex Point
|
|
uint32 MaterialIndex;
|
|
FVector3f Position;
|
|
|
|
// Additional weight used to select against collapse.
|
|
|
|
float SpecializedWeight;
|
|
|
|
// ---- Vertex Attributes ------------------------------------------------------------------
|
|
// 3 Types: Dense Attributes, Sparse Attributes, Bones
|
|
// Dense & Sparse Attributes are used in quadric calculation
|
|
// Bones are excluded from the quadric error, but maybe used in imposing penalties for collapse.
|
|
|
|
// - Base Attributes: Normal, Tangent, BiTangent, Color, TexCoords: size = 13 + 2 * NumTexCoord
|
|
BasicAttrContainerType BasicAttributes;
|
|
|
|
// - Additional Attributes : size not determined at compile time
|
|
AttrContainerType AdditionalAttributes;
|
|
|
|
// - Sparse Bones : size not determined at compile time
|
|
BoneContainer SparseBones;
|
|
|
|
public:
|
|
|
|
typedef typename BasicAttrContainerType::DenseAttrAccessor DenseAttrAccessor;
|
|
|
|
|
|
TSkeletalSimpVert() :
|
|
ClosestSrcVertIndex(-1),
|
|
MaterialIndex(0),
|
|
Position(ForceInitToZero),
|
|
SpecializedWeight(0.f),
|
|
BasicAttributes(),
|
|
AdditionalAttributes(),
|
|
SparseBones()
|
|
{
|
|
}
|
|
|
|
// copy constructor
|
|
TSkeletalSimpVert(const TSkeletalSimpVert& Other) :
|
|
ClosestSrcVertIndex(Other.ClosestSrcVertIndex),
|
|
MaterialIndex(Other.MaterialIndex),
|
|
Position(Other.Position),
|
|
SpecializedWeight(Other.SpecializedWeight),
|
|
BasicAttributes(Other.BasicAttributes),
|
|
AdditionalAttributes(Other.AdditionalAttributes),
|
|
SparseBones(Other.SparseBones)
|
|
{}
|
|
|
|
|
|
|
|
uint32 GetMaterialIndex() const { return MaterialIndex; }
|
|
FVector3f& GetPos() { return Position; }
|
|
const FVector3f& GetPos() const { return Position; }
|
|
|
|
|
|
// Access to the base attributes. Note these are really floats.
|
|
|
|
static int32 NumBaseAttributes() { return BasicAttrContainerType::Size(); }
|
|
DenseAttrAccessor GetBasicAttrAccessor() { return BasicAttributes.AsDenseAttrAccessor(); }
|
|
const DenseAttrAccessor GetBasicAttrAccessor() const { return BasicAttributes.AsDenseAttrAccessor(); }
|
|
|
|
// Additional attributes maybe dense or sparse. This should hold bone weights and the like.
|
|
|
|
AttrContainerType& GetAdditionalAttrContainer() { return AdditionalAttributes; }
|
|
const AttrContainerType& GetAdditionalAttrContainer() const { return AdditionalAttributes; }
|
|
|
|
|
|
// Bones, not used in Quadric calculation.
|
|
|
|
BoneContainer& GetSparseBones() { return SparseBones; }
|
|
const BoneContainer& GetSparseBones() const { return SparseBones; }
|
|
|
|
// Insure that the attribute values are valid
|
|
// by correcting any invalid ones.
|
|
|
|
void Correct()
|
|
{
|
|
// This fixes the normal, tangent, and bi-tangent.
|
|
BasicAttributes.Correct();
|
|
|
|
AdditionalAttributes.Correct();
|
|
|
|
SparseBones.Correct();
|
|
}
|
|
|
|
|
|
|
|
TSkeletalSimpVert& operator=(const TSkeletalSimpVert& Other)
|
|
{
|
|
ClosestSrcVertIndex = Other.ClosestSrcVertIndex;
|
|
MaterialIndex = Other.MaterialIndex;
|
|
Position = Other.Position;
|
|
SpecializedWeight = Other.SpecializedWeight;
|
|
BasicAttributes = Other.BasicAttributes;
|
|
AdditionalAttributes = Other.AdditionalAttributes;
|
|
SparseBones = Other.SparseBones;
|
|
|
|
return *this;
|
|
}
|
|
|
|
// Tests approximate equality using specialized
|
|
// comparison functions.
|
|
// NB: This functionality exists to help in welding verts
|
|
// prior to simplification, but is not used in the simplifier itself.
|
|
bool Equals(const VertType& Other) const
|
|
{
|
|
bool bIsApprxEquals =
|
|
(MaterialIndex == Other.MaterialIndex)
|
|
&& (ClosestSrcVertIndex == Other.ClosestSrcVertIndex)
|
|
&& PointsEqual(Position, Other.Position);
|
|
|
|
bIsApprxEquals = bIsApprxEquals
|
|
&& FMath::IsNearlyEqual(SpecializedWeight, Other.SpecializedWeight, 1.e-5);
|
|
|
|
bIsApprxEquals = bIsApprxEquals
|
|
&& BasicAttributes.IsApproxEquals(Other.BasicAttributes);
|
|
|
|
bIsApprxEquals = bIsApprxEquals
|
|
&& AdditionalAttributes.IsApproxEquals(Other.AdditionalAttributes);
|
|
|
|
bIsApprxEquals = bIsApprxEquals
|
|
&& SparseBones.IsApproxEquals(Other.SparseBones);
|
|
return bIsApprxEquals;
|
|
}
|
|
|
|
// Exact equality tests
|
|
|
|
bool operator==(const VertType& Other) const
|
|
{
|
|
bool bIsEqual = (MaterialIndex == Other.MaterialIndex) &&
|
|
(ClosestSrcVertIndex == Other.ClosestSrcVertIndex) &&
|
|
(Position == Other.Position);
|
|
bIsEqual = bIsEqual && (SpecializedWeight == Other.SpecializedWeight);
|
|
bIsEqual = bIsEqual && (GetBasicAttrAccessor() == Other.GetBasicAttrAccessor());
|
|
bIsEqual = bIsEqual && (GetAdditionalAttrContainer() == Other.GetAdditionalAttrContainer());
|
|
bIsEqual = bIsEqual && (SparseBones == Other.SparseBones);
|
|
|
|
return bIsEqual;
|
|
}
|
|
|
|
// Standard operator overloading.
|
|
// Note: these don't affect the ClosestSrcVertIndex or MaterialIndex
|
|
|
|
VertType operator+(const VertType& Other) const
|
|
{
|
|
VertType Result(*this);
|
|
Result.Position += Other.Position;
|
|
|
|
Result.SpecializedWeight = FMath::Max(Result.SpecializedWeight, Other.SpecializedWeight);
|
|
|
|
auto BaseAttrs = Result.GetBasicAttrAccessor();
|
|
BaseAttrs += Other.GetBasicAttrAccessor();
|
|
|
|
AttrContainerType& SparseAttrs = Result.GetAdditionalAttrContainer();
|
|
SparseAttrs += Other.GetAdditionalAttrContainer();
|
|
|
|
SparseBones += Other.SparseBones;
|
|
|
|
return Result;
|
|
}
|
|
|
|
VertType operator-(const VertType& Other) const
|
|
{
|
|
VertType Result(*this);
|
|
|
|
Result.Position -= Other.Position;
|
|
|
|
Result.SpecializedWeight = FMath::Max(Result.SpecializedWeight, Other.SpecializedWeight);
|
|
|
|
auto BaseAttrs = Result.GetBasicAttrAccessor();
|
|
BaseAttrs -= Other.GetBasicAttrAccessor();
|
|
|
|
AttrContainerType& SparseAttrs = Result.GetAdditionalAttrContainer();
|
|
SparseAttrs -= Other.GetAdditionalAttrContainer();
|
|
|
|
SparseBones -= Other.SparseBones;
|
|
return Result;
|
|
}
|
|
|
|
VertType operator*(const float Scalar) const
|
|
{
|
|
VertType Result(*this);
|
|
Result.Position *= Scalar;
|
|
|
|
auto BaseAttrs = Result.GetBasicAttrAccessor();
|
|
BaseAttrs *= Scalar;
|
|
|
|
AttrContainerType& SparseAttrs = Result.GetAdditionalAttrContainer();
|
|
SparseAttrs *= Scalar;
|
|
|
|
BoneContainer& ResultBones = Result.GetSparseBones();
|
|
|
|
ResultBones *= Scalar;
|
|
return Result;
|
|
}
|
|
|
|
VertType operator/(const float Scalar) const
|
|
{
|
|
float invScalar = 1.0f / Scalar;
|
|
return (*this) * invScalar;
|
|
}
|
|
};
|
|
}
|
|
}
|