// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "SkeletalSimplifierLinearAlgebra.h" namespace SkeletalSimplifier { namespace Quadrics { // For the vectors, matrices and such using namespace SkeletalSimplifier::LinearAlgebra; // Quadric to preserve the geometry of discontinuity class FEdgeQuadric { public: FEdgeQuadric() {} FEdgeQuadric(const Vec3d& Vert0Pos, const Vec3d& Vert1Pos, const Vec3d& FaceNormal, const double EdgeWeight); FEdgeQuadric(const FEdgeQuadric&) = default; FEdgeQuadric& operator=(const FEdgeQuadric&) = default; double Evaluate(const Vec3d& Pos) const; FEdgeQuadric& operator+=(const FEdgeQuadric& Other); void Zero() { CMatrix.Zero(); D0Vector.Zero(); CScalar = 0.; } private: // NB: these all default construct with zero value // This contains the minimal data required for the geometric bits of a quadric /** * 3 * Quadric Matrix ( C_Matrix ) 3 * * Quadric Vector (D0_Vector) 3 * Quadric Scalar CScalar size 1 */ // quadric matrix SymmetricMatrix CMatrix; // 3x3 - Symmetric matrix // Quadratic vector Vec3d D0Vector; // 3 - vector // Quadric scalar double CScalar = 0.; // 1 - scalar // Allow the optimizer access to the member data. This is used in adding FEdgeQuadric to the Optimizer template friend class TQuadricOptimizer; }; // Base class for the more general Quadric that includes two arrays of attributes. // The first array is dense, and the second array is optionally sparse. template class TQuadricBase { public: typedef typename BasicAttrContainerType::DenseBMatrixType B1MatrixType; typedef typename TVectorToMatrixTrait::BMatrixType B2MatrixType; typedef Vec3d D0VectorType; typedef typename BasicAttrContainerType::DenseVecDType D1VectorType; typedef AdditionalAttrContainerType D2VectorType; TQuadricBase() {} TQuadricBase(const TQuadricBase& Other) : CMatrix(Other.CMatrix), B1Matrix(Other.B1Matrix), B2Matrix(Other.B2Matrix), Gamma(Other.Gamma), D0Vector(Other.D0Vector), D1Vector(Other.D1Vector), D2Vector(Other.D2Vector), CScalar(Other.CScalar), VolDistConstraint(Other.VolDistConstraint), VolGradConstraint(Other.VolGradConstraint), UVBBox(Other.UVBBox) {} TQuadricBase& operator+=(const TQuadricBase& Other); TQuadricBase& operator=(const TQuadricBase& Other); /** * The state vector is broken into three parts: State = {Pos, S1, S2} * * This returns double = < State, Quadic_Matrix * State> + 2 + Quadric_Scalar */ double EvaluateQaudric(const D0VectorType& Pos, const D1VectorType& S1, const D2VectorType& S2) const; protected: // NB: these all default construct with zero value /** * 3 N M * Quadric Matrix ( C_Matrix, B1_Matrix, B2_Matrix ) 3 * ( B1_Matrix^T, Gamma * I, 0 ) N * ( B2_Matrix^T, 0 Gamma *I ) M * * Quadric Vector (D0_Vector) 3 * (D1_Vector) N * (D2_Vector) M * * Quadric Scalar CScalar size 1 */ // quadric matrix SymmetricMatrix CMatrix; // 3x3 - Symmetric B1MatrixType B1Matrix; // 3xN - BasicAttr Gradients B2MatrixType B2Matrix; // 3xM - (sparse) AddionalAttr Gradients double Gamma = 0.; // diagonal identity in quadric matrix // quadric vector D0VectorType D0Vector; // 3 D1VectorType D1Vector; // N D2VectorType D2Vector; // M (sparse) // quadric scalar double CScalar = 0.; // volume constraint double VolDistConstraint = 0.; Vec3d VolGradConstraint; // used to clamp 1st UV FAABBox2d UVBBox; }; // Wedge type Quadric that contains two arrays of attributes. // The first is dense, and the second may be sparse. template class TFaceQuadric : public TQuadricBase { public: typedef TQuadricBase MyBase; typedef SimpVertexType_ SimpVertexType; typedef typename SimpVertexType::DenseAttrAccessor DenseAttrAccessor; typedef typename SimpVertexType::AttrContainerType AttrContainerType; typedef typename SimpVertexType::BasicAttrContainerType BasicAttrContainerType; typedef SparseWeightArrayType_ SparseWeightContainerType; typedef typename MyBase::B1MatrixType B1MatrixType; typedef typename MyBase::B2MatrixType B2MatrixType; typedef typename MyBase::D0VectorType D0VectorType; typedef typename MyBase::D1VectorType D1VectorType; typedef typename MyBase::D2VectorType D2VectorType; using MyBase::CMatrix; using MyBase::B1Matrix; using MyBase::B2Matrix; using MyBase::Gamma; using MyBase::D0Vector; using MyBase::D1Vector; using MyBase::D2Vector; using MyBase::CScalar; using MyBase::VolGradConstraint; using MyBase::VolDistConstraint; using MyBase::UVBBox; /** * @param TriVert0 - The three verts that define the triangle face. * @param TriVert1 - * @param TriVert2 - * NB: These have to be some form of TSkeletalSimpVerts * * @param BasicWeights - The weights to be applied to the BasicAttributes * @param AdditionalWeights - The weights to be applied to the AdditionalAttributes */ TFaceQuadric(const SimpVertexType& TriVert0, const SimpVertexType& TriVert1, const SimpVertexType& TriVert2, const D1VectorType& BasicWeights, const SparseWeightContainerType& AdditionalWeights, const bool bUseLegacyAttrGrad); TFaceQuadric() = default; TFaceQuadric(const TFaceQuadric& Other) = default; TFaceQuadric& operator=(const TFaceQuadric& Other) = default; /** * Returns the quadric evaluation at this point (vTr . A . v + 2 bTr. v + c) where A = quadric matrix, b = DVector, c = CScalar * with bra-ket + 2 + c */ double Evaluate(const SimpVertexType& Vert, const D1VectorType& BasicWeights, const SparseWeightContainerType& AdditionalWeights) const; /** * @param Vert - Update Vert.BasicAttributes and Vert.AdditionalAttributes to the values interpolated at Vert.Position. * NB: assumes Vert.Position is already defined * * @param BasicWeights - The weights to be applied to the BasicAttributes * @param AdditionalWeights - The weights to be applied to the AdditionalAttributes */ void CalcAttributes(SimpVertexType& Vert, const D1VectorType& BasicWeights, const SparseWeightArrayType_& AdditionalWeights) const; TFaceQuadric& operator+=(const TFaceQuadric& Other) { MyBase::operator+=(Other); return *this; } double TotalArea() const { return Gamma; } private: /** * -- Private: Used in the constructor -- * * Computes the Gradient coefficient for each attribute index and stores them in the ResultMatrix. The resulting "distances" are stored in the result vector * * @param GradientTool - Tool used to solve for the interpolating gradient and distances (position0^t , 1) ( g_i[0] ) = (vert0Attr[i]) * (position1^t , 1) ( g_i[1] ) (vert1Attr[i]) * (position2^t, 1) ( g_i[2] ) (vert2Attr[i]) * (faceNormal^t, 0) ( d_i ) ( 0 ) * @param Vert0Attrs - Attribute containers for each vertex in the triangle * @param Vert1Attrs * @param Vert2Attrs * * @param Weights - Weights to scale the attributes with. * * @param GradientMatrix - Matrix with columns that are the "-gradient" (-g_i) of attributes * @param DistanceVector - The "-distances" (-d_i) for each attribute. * * @return 'false' if the gradient encoding failed and the Gradient Matrix is empty. In either event, the Distance Vector holds useful stuff. */ template bool EncodeAttrGradient(const InverseGradientProjection& GradientTool, const VectorType& Vert0Attrs, const VectorType& Vert1Attrs, const VectorType& Vert2Attrs, const WeightsArrayType& Weights, MatrixType& GradientMatrix, VectorType& DistanceVector); /** * -- Private: Used in the constructor -- * * Weight the quadric values by an area. */ void WeightByArea(double Area); // Helpers /** * If the weight > 1.e-6 then compute the interpolated attribute values from the gradient matrix and distances. * otherwise set the attr to zero; * * Attr[i] = 1 / (weight(i) * area) * ( < Pos | GradientMatrix.GetColumn(i) > + Dist(i) ) */ static void ComputeAttrs(const double Area, const B1MatrixType& GradientMatrix, const D1VectorType& DistVector, const Vec3d& Pos, const D1VectorType& Weights, DenseAttrAccessor& Attrs); static void ComputeAttrs(const double Area, const SparseBMatrix& GradientMatrix, const SparseVecD& DistVector, const Vec3d& Pos, const SparseWeightContainerType& Weights, SparseVecD& Attrs); static void SumOuterProducts(const B1MatrixType& GradientArray, const D1VectorType& DistArray, SymmetricMatrix& OuterProductSum, Vec3d& DistGradientSum); static void SumOuterProducts(const SparseBMatrix& GradientArray, const SparseVecD& DistArray, SymmetricMatrix& OuterProductSum, Vec3d& DistGradientSum); }; // Tool used to accumulate the quadric values and find the optimal position. template class TQuadricOptimizer : public TQuadricBase { public: typedef typename FaceQuadricType::SimpVertexType SimpVertexType; typedef typename FaceQuadricType::AttrContainerType AttrContainerType; typedef typename FaceQuadricType::BasicAttrContainerType BaseAttrContainerType; typedef TQuadricBase MyBase; typedef FaceQuadricType FFaceQuadric; using MyBase::CMatrix; using MyBase::B1Matrix; using MyBase::B2Matrix; using MyBase::Gamma; using MyBase::D0Vector; using MyBase::D1Vector; using MyBase::D2Vector; using MyBase::CScalar; using MyBase::VolGradConstraint; using MyBase::VolDistConstraint; using MyBase::UVBBox; TQuadricOptimizer() : MyBase() {} void AddFaceQuadric(const FFaceQuadric& FaceQuadric) { MyBase::operator+=(FaceQuadric); }; void AddEdgeQuadric(const FEdgeQuadric& EdgeQuadric) { // The edge quadric only hold the geometric parts of a quadric CMatrix += EdgeQuadric.CMatrix; D0Vector += EdgeQuadric.D0Vector; CScalar += EdgeQuadric.CScalar; } /** @param OptimalPosition - Result of optimizing the quadric @param bPreserveVolume - Controls the use of the volume conservation fixup. @param VolumeImportance - Artificial scalar between 0, 1 that scales the amount of conservation fixup actually used. the value 0 corresponds to no fixup, and 1 to full fixup */ bool Optimize(Vec3d& OptimalPosition, const bool bPreserveVolume, const double VolumeImportance) const; }; // --- Implementations. // ---- Helpers static void InitializeBMatrix(const TArray& IterMask, SparseBMatrix& Matrix) { Matrix.Reset(); for (int i = 0, iMax = IterMask.Num(); i < iMax; ++i) { if (IterMask[i] == 0) continue; Matrix.SetColumn(i, Vec3d(0., 0., 0.)); } } template static void InitializeBMatrix(const DenseIterMask& IterMask, TDenseBMatrix& Matrix) { // Zero Matrix Matrix.Reset(); } static void InitializeBVector(const TArray& IterMask, SparseVecD& Vec) { Vec.Reset(); for (int i = 0, iMax = IterMask.Num(); i < iMax; ++i) { if (IterMask[i] == 0) continue; Vec.SetElement(i, 0.); } } template static void InitializeBVector(const DenseIterMask& IterMask, TDenseVecD& Vec) { // Zero Vector Vec.Reset(); } // --- Quadric Base template inline double TQuadricBase::EvaluateQaudric(const D0VectorType& Pos, const D1VectorType& S1, const D2VectorType& S2) const { // S = {pos, s1, s2} // < s | Quadric_Matrix * s> = // < pos | C * pos> + 2 ( < pos | B1 * s1 > + < pos | B2 * s2 > ) + Gamma* ( < s1 | s1 > + < s2 | s2 > ) double SQmS = 0.; { double Pt1 = Pos.DotProduct(CMatrix * Pos); double Pt2 = 2. * Pos.DotProduct(B1Matrix * S1 + B2Matrix * S2); double Pt3 = Gamma * ( S1.L2NormSqr() + S2.L2NormSqr() ); SQmS = Pt1 + Pt2 + Pt3; } // quadric_vector = {D0Vector, D1Vector, D2Vector} // // 2. * < s | quadratic_vector > = // 2.* ( < pos | D0Vector > + < s1 | D1Vector > + < s2 | D2Vector > ) double CrossTerm = 0.; { double Pt1 = Pos.DotProduct(D0Vector); double Pt2 = S1.DotProduct(D1Vector); double Pt3 = S2.DotProduct(D2Vector); CrossTerm = 2. * (Pt1 + Pt2 + Pt3); } // < s | Quadric_Matrix * s > + 2 < s | quadric_vector > + quadric_scalar return SQmS + CrossTerm + CScalar; } template inline TQuadricBase& TQuadricBase::operator+=(const TQuadricBase& Other) { CMatrix += Other.CMatrix; B1Matrix += Other.B1Matrix; B2Matrix += Other.B2Matrix; D0Vector += Other.D0Vector; D1Vector += Other.D1Vector; D2Vector += Other.D2Vector; CScalar += Other.CScalar; Gamma += Other.Gamma; VolGradConstraint += Other.VolGradConstraint; VolDistConstraint += Other.VolDistConstraint; UVBBox += Other.UVBBox; return *this; } template inline TQuadricBase& TQuadricBase::operator=(const TQuadricBase& Other) { CMatrix = Other.CMatrix; B1Matrix = Other.B1Matrix; B2Matrix = Other.B2Matrix; D0Vector = Other.D0Vector; D1Vector = Other.D1Vector; D2Vector = Other.D2Vector; CScalar = Other.CScalar; Gamma = Other.Gamma; VolGradConstraint = Other.VolGradConstraint; VolDistConstraint = Other.VolDistConstraint; UVBBox = Other.UVBBox; return *this; } // ---- FEdgeQuadirc --- inline FEdgeQuadric::FEdgeQuadric(const Vec3d& Vert0Pos, const Vec3d& Vert1Pos, const Vec3d& FaceNormal, const double EdgeWeight) { // Early out if the face normal doesn't have unit length. // In practice the normal was computed in single precision, and may have failed if the triangle was degenerate. // This test exists largely to catch the case where the normal failed. // NB: this threshold is only 0.01 { double LenghtSqrd = FaceNormal.LengthSqrd(); bool bIsUnitLenght = (FMath::Abs(LenghtSqrd - 1.) < THRESH_VECTOR_NORMALIZED); if (!bIsUnitLenght) { return; } } Vec3d Edge = Vert1Pos - Vert0Pos; // Weight scaled on edge length const double EdgeLength = std::sqrt(Edge.LengthSqrd()); const double Weight = EdgeWeight * EdgeLength; if (EdgeLength < 1.e-8) { return; } else { Edge *= 1. / EdgeLength; } // Normal that is perpendicular to the edge, and face. The constraint should try // to keep points on the plane associated with this constraint plane. Vec3d N = CrossProduct(Edge, FaceNormal); // Make the n unit length and record the old length for use in computing the area. double Length = N.LengthSqrd(); if (Length < 1.e-8) { // @todo make sure everything is Zero(); return; } else { // normalize N *= 1. / std::sqrt(Length); } double Dist = -N.DotProduct(Vert0Pos); // - n.dot.p0 CMatrix = ScaledProjectionOperator(N); // N . NTranspose D0Vector = Dist * N; // Dist * N; CScalar = Dist * Dist; // Dist * Dist // Scale by weight CMatrix *= Weight; D0Vector *= Weight; CScalar *= Weight; } inline double FEdgeQuadric::Evaluate(const Vec3d& Pos) const { // < pos | C_Matrix pos > + 2 < pos | D0_Vector > + CScalar Vec3d CmPos = CMatrix * Pos; double Result = Pos.DotProduct(CmPos) + 2. * Pos.DotProduct(D0Vector) + CScalar; return Result; } inline FEdgeQuadric& FEdgeQuadric::operator+=(const FEdgeQuadric& Other) { CMatrix += Other.CMatrix; D0Vector += Other.D0Vector; CScalar += Other.CScalar; return *this; } // --- Face Quadric template TFaceQuadric::TFaceQuadric(const SimpVertexType& TriVert0, const SimpVertexType& TriVert1, const SimpVertexType& TriVert2, const D1VectorType& BasicWeights, const SparseWeightContainerType& AdditionalWeights, const bool bUseLegacyAttrGrad) { // Convert point locations to double. const Vec3d Vert0Pos(TriVert0.GetPos()); const Vec3d Vert1Pos(TriVert1.GetPos()); const Vec3d Vert2Pos(TriVert2.GetPos()); // The normal direction ( but not necessarily unit length) Vec3d FaceNormal = CrossProduct(Vert2Pos - Vert0Pos, Vert1Pos - Vert0Pos); // n = (p2 - p0) ^ (p1 - p0); // Make the FaceNormal unit length and record the old length for use in computing the area. double LengthSqrd = FaceNormal.LengthSqrd(); double Area; // = 1/2 | edge X edge | if (LengthSqrd < SMALL_NUMBER) { // Face was too small. // Allocate defaults for the quadric and return. D1Vector.Reset(); B1Matrix.Reset(); // @todo make sure everything is Zero(); return; } else { // normalize double Length = std::sqrt(LengthSqrd); FaceNormal *= 1. / Length; Area = 0.5 * Length; // = 1/2 | edge X edge | } // Dist + FaceNormal.Dot.P = 0; // for any p on the face. double Dist = -FaceNormal.DotProduct(Vert0Pos); // - n.dot.p0 // For volume constraint: FaceNormal.Dot(Pos) + Dist = 0 VolGradConstraint = FaceNormal * (1. / 3.); // this 1/3 is shared by all faces, it ultimately drops out... VolDistConstraint = Dist * (1. / 3.); // Initialize parts of the quadric with the Geometric component CMatrix = ScaledProjectionOperator(FaceNormal); // N . NTranspose D0Vector = Dist * FaceNormal; // Dist * N; CScalar = Dist * Dist; // Dist * Dist // create the tool needed to compute the gradient coefficients of the attributes. InverseGradientProjection GradientTool(Vert0Pos, Vert1Pos, Vert2Pos, FaceNormal, bUseLegacyAttrGrad); // Accumulate the terms related to the gradient of the Basic Attributes { // Update the UV BBox UVBBox.ExpandToInclude(TriVert0.BasicAttributes.TexCoords[0]); UVBBox.ExpandToInclude(TriVert1.BasicAttributes.TexCoords[0]); UVBBox.ExpandToInclude(TriVert2.BasicAttributes.TexCoords[0]); // Construct double precision arrays from the float data held by the vertex. const D1VectorType Vert0BasicAttrs(TriVert0.GetBasicAttrAccessor()); const D1VectorType Vert1BasicAttrs(TriVert1.GetBasicAttrAccessor()); const D1VectorType Vert2BasicAttrs(TriVert2.GetBasicAttrAccessor()); const bool bHasGradients = EncodeAttrGradient(GradientTool, Vert0BasicAttrs, Vert1BasicAttrs, Vert2BasicAttrs, BasicWeights, B1Matrix, D1Vector); CScalar += D1Vector.L2NormSqr(); if (bHasGradients) { // Accumulate the outer product of the attribute gradient in the CMatrix: CMatrix += Sum ( outer_product(B1Matrix.GetColumn(i)) ) // Accumulate the scaled attribute distance vector in D0Vector: D0Vector += Sum( D1[i] * B1Matrix.GetColumn(i) ) SumOuterProducts(B1Matrix, D1Vector, CMatrix, D0Vector); } } // Accumulate the terms related the the Additional Attributes. { const D2VectorType& Vert0AdditionalAttrs = TriVert0.GetAdditionalAttrContainer(); const D2VectorType& Vert1AdditionalAttrs = TriVert1.GetAdditionalAttrContainer(); const D2VectorType& Vert2AdditionalAttrs = TriVert2.GetAdditionalAttrContainer(); const bool bHasGradeints = EncodeAttrGradient(GradientTool, Vert0AdditionalAttrs, Vert1AdditionalAttrs, Vert2AdditionalAttrs, AdditionalWeights, B2Matrix, D2Vector); CScalar += D2Vector.L2NormSqr(); if (bHasGradeints) { // Accumulate the outer product of the attribute gradient in the CMatrix: CMatrix += Sum ( outer_product(B1Matrix.GetColumn(i)) ) // Accumulate the scaled attribute distance vector in D0Vector: D0Vector += Sum( D1[i] * B1Matrix.GetColumn(i) ) SumOuterProducts(B2Matrix, D2Vector, CMatrix, D0Vector); } } // Store the area Gamma = Area; // Weight the quadric matrix, vector, and scalar by area WeightByArea(Area); } template template bool TFaceQuadric::EncodeAttrGradient( const InverseGradientProjection& GradientTool, const VectorType& Vert0Attrs, const VectorType& Vert1Attrs, const VectorType& Vert2Attrs, const WeightsArrayType& Weights, MatrixType& GradientMatrix, VectorType& DistanceVector) { // Need the union of the possibility sparse attrs iteration range. auto IterMask = SkeletalSimplifier::LinearAlgebra::GetIterationMask(Vert0Attrs, Vert1Attrs, Vert2Attrs); // Initialize the BMatrix (This holds -AttrGrad) and B2Vector (holds -AttrDist) InitializeBMatrix(IterMask, GradientMatrix); InitializeBVector(IterMask, DistanceVector); const bool bCanFindGradients = GradientTool.IsValid(); // compute the gradient coefficients for each attribute, // storing the results in the BMatrix and the associated distances in the B2Vector if (bCanFindGradients) // non-degenerate triangle. { Vec3d PerVertexData; for ( int32 i = 0; i < IterMask.Num(); ++i ) { // no non-zero element if (IterMask[i] == 0) continue; const double weight = Weights.GetElement(i); if (weight < 1.e-6) continue; // get the data for this attribute at the three corners of the triangle PerVertexData[0] = Vert0Attrs.GetElement(i); PerVertexData[1] = Vert1Attrs.GetElement(i); PerVertexData[2] = Vert2Attrs.GetElement(i); PerVertexData *= weight; // Compute the Attribute Grad and AttrDist Vec3d AttrGrad; double AttrDist = GradientTool.ComputeGradient(PerVertexData, AttrGrad); // Store in the GradientMatrix. GradientMatrix.SetColumn(i, -1. * AttrGrad); // Store in Distance Vector DistanceVector.SetElement(i, -1. * AttrDist); } } else { // If the triangle was degenerate (the gradient tool will be invalid), then just use the average // of the attributes and implicitly AttrGrad = 0; Vec3d PerVertexData; for ( int32 i = 0; i < IterMask.Num(); ++i ) { // no non-zero element if (IterMask[i] == 0) continue; const double weight = Weights.GetElement(i); if (weight < 1.e-6) continue; // get the data for this attribute at the three corners of the triangle PerVertexData[0] = Vert0Attrs.GetElement(i); PerVertexData[1] = Vert1Attrs.GetElement(i); PerVertexData[2] = Vert2Attrs.GetElement(i); PerVertexData *= weight; double AverageAttr = (1. / 3.) * (PerVertexData[0] + PerVertexData[1] + PerVertexData[2]); GradientMatrix.SetColumn(i, Vec3d(0., 0., 0.)); DistanceVector.SetElement(i, -1. * AverageAttr); } } return bCanFindGradients; } template void TFaceQuadric::WeightByArea(double Area) { // Weight by area CMatrix *= Area; B1Matrix *= Area; B2Matrix *= Area; D0Vector *= Area; D1Vector *= Area; D2Vector *= Area; CScalar *= Area; VolDistConstraint *= Area; VolGradConstraint *= Area; } template inline void TFaceQuadric::ComputeAttrs(const double Area, const B1MatrixType& GradientMatrix, const D1VectorType& DistVector, const Vec3d& Pos, const D1VectorType& Weights, DenseAttrAccessor& Attrs) { checkSlow(Attrs.Num() == DistVector.Num()); for (int32 i = 0; i < DistVector.Num(); ++i) { double weight = Weights.GetElement(i); double AttrValue = 0.; if (!(weight < 1.e-6)) { // GradientMatrix.GetColum(i) = -g_i // DistVector.GetElement(i) = -d_i AttrValue = Pos.DotProduct( GradientMatrix.GetColumn(i) ) + DistVector.GetElement(i); AttrValue /= (weight * Area); } Attrs[i] = -AttrValue; } } template inline void TFaceQuadric::ComputeAttrs(const double Area, const SparseBMatrix& GradientMatrix, const SparseVecD& DistVector, const Vec3d& Pos, const SparseWeightContainerType& Weights, SparseVecD& Attrs) { Attrs.Reset(); // Empty this sparse data structure. for (auto citer = GradientMatrix.GetData().CreateConstIterator(); citer; ++citer) { int32 idx = citer->Key; double weight = Weights.GetElement(idx); double AttrValue = 0.; if (!(weight < 1.e-6)) { AttrValue = Pos.DotProduct(citer->Value) + DistVector.GetElement(idx); AttrValue /= (weight * Area); } Attrs.SetElement(idx, -AttrValue); } } template inline void TFaceQuadric::CalcAttributes(SimpVertexType& Vert, const D1VectorType& BasicWeights, const SparseWeightArrayType& AdditionalWeights) const { // convert to double precision. const Vec3d Pos = Vert.GetPos(); // Update the basic attrs auto BasicAttrAccessor = Vert.GetBasicAttrAccessor(); ComputeAttrs(Gamma, B1Matrix, D1Vector, Pos, BasicWeights, BasicAttrAccessor); // Clamp first UV channel to a slightly padded version of the UV support. const float PaddingFactor = 0.2f; UVBBox.ClampPoint(Vert.BasicAttributes.TexCoords[0], PaddingFactor); // Update the Additional Attrs auto& AddionalAttrs = Vert.GetAdditionalAttrContainer(); ComputeAttrs(Gamma, B2Matrix, D2Vector, Pos, AdditionalWeights, AddionalAttrs); } template inline double TFaceQuadric::Evaluate(const SimpVertexType& Vert, const D1VectorType& BasicWeights, const SparseWeightArrayType& AdditionalWeights) const { // Convert position to double const Vec3d Pos = Vert.GetPos(); // Need scaled version of the state const auto BasicAttrsAccessor = Vert.GetBasicAttrAccessor(); D1VectorType S1; checkSlow(BasicAttrsAccessor.Num() == BasicWeights.Num()); checkSlow(S1.Num() == BasicAttrsAccessor.Num()); for (int32 i = 0; i < S1.Num(); ++i) { S1.SetElement(i, BasicAttrsAccessor[i] * BasicWeights[i]); } const auto& AdditionalAttrs = Vert.GetAdditionalAttrContainer(); D2VectorType S2; for (auto citer = AdditionalAttrs.GetData().CreateConstIterator(); citer; ++citer) { const int32 idx = citer->Key; double Value = citer->Value; double weight = AdditionalWeights.GetElement(idx); S2.SetElement(idx, weight * Value); } // Evaluate the quadric using the scaled state values. return MyBase::EvaluateQaudric(Pos, S1, S2); } template inline void TFaceQuadric::SumOuterProducts(const B1MatrixType& GradientArray, const D1VectorType& DistArray, SymmetricMatrix& OuterProductSum, Vec3d& DistGradientSum) { checkSlow(DistArray.Num() == GradientArray.NumCols()); const int32 NumElements = DistArray.Num(); for (int32 i = 0; i < NumElements; ++i) { double Dist = DistArray[i]; const Vec3d& Gradient = GradientArray.GetColumn(i); DistGradientSum += Dist * Gradient; OuterProductSum += ScaledProjectionOperator(Gradient); } } template inline void TFaceQuadric::SumOuterProducts(const SparseBMatrix& GradientArray, const SparseVecD& DistArray, SymmetricMatrix& OuterProductSum, Vec3d& DistGradientSum) { const auto& SparseData = DistArray.GetData(); for (auto citer = SparseData.CreateConstIterator(); citer; ++citer) { double Dist = citer->Value; const Vec3d& Gradient = GradientArray.GetColumn(citer->Key); DistGradientSum += Dist * Gradient; OuterProductSum += ScaledProjectionOperator(Gradient); } } //--- Optimizer template inline bool TQuadricOptimizer::Optimize(Vec3d& OutPosition, const bool bPreserveVolume, const double VolumeImportance) const { /** * Optimizing the quadric requires inverting a matrix system: * * 3 N M * Quadric Matrix ( C_Matrix, B1_Matrix, B2_Matrix ) 3 * ( B1_Matrix^T, Gamma * I, 0 ) N * ( B2_Matrix^T, 0 , Gamma *I ) M * * Quadric Vector ( D0_Vector ) 3 * ( D1_Vector ) N * ( D2_Vector ) M * * Solve: * Quadric_Matrix * Vector = -Quadric_Vector * * for Vector. Here Vector = ( Pos ) 3 * ( S1 ) N * ( S2 ) M * with "Pos" being the position and S1 & S2 are state for the dense (S1) and sparse (S2) attributes. * * To solve, a little manipulation shows that the state can be computed from the position * * S1 = -1/gamma ( D1_Vector + B1_Matrix^T * Pos ) * S2 = -1/gamma ( D2_Vector + B2_Matrix^T * Pos ) * * and the position can be obtained by inverting a symmetric 3x3 matrix, * * [ C_Matrix - 1/gamma (B1_Matrix * B1_Matrix^T + B2_Matrix * B2_Matrix^T ) ] * Pos = * 1/gamma [B1_Matrix * D1_Vector + B2_Matrix * D2_Vector] - D0_Vector * * Lhs_Matrix * Pos = Rhs_Vector * * The optimal position is given by Pos = Inverse(Lhs_Matrix) * Rhs_Vector * * If Volume Preservation is desired, a scalar Lagrange multiplier 'lm' is used to inflate the system * * * 3 1 * ( Lhs_Matrix, Vol_Gradient ) ( Pos ) = ( Rhs_Vector ) * ( Vol_Gradient^T, 0 ) ( lm ) ( -Vol_Grad_Dist ) * * * lm = (Vol_Grad_Dist + ) / * * Pos = InvL * Rhs_Vec - lm InvL * Vol_Grad * * where InvL = Lhs_Matrix.Inverse() * * Note: InvL * Rhs_Vec is the unconstrained solution (if you ignored volume preservation) * and -lm * InvL * Vol_Grad is the correction. * * notation: * = DotProduct(VectorA, VectorB). */ const double Threshold = 1.e-12; if (Gamma < Threshold) return false; // LHS SymmetricMatrix LhsMatrix = CMatrix - (1. / Gamma) * ( OuterProductOperator(B1Matrix) + OuterProductOperator(B2Matrix) ); // Invert: bool bSucces = true; const SymmetricMatrix InvLhsMatrix = LhsMatrix.Inverse(bSucces, Threshold); // Return false if we can't find any optimal position. if (bSucces) { // RHS Vec3d RhsVector = (1. / Gamma) * (B1Matrix * D1Vector + B2Matrix * D2Vector) - D0Vector; // The optimal position with out the volume constraint OutPosition = InvLhsMatrix * RhsVector; if (bPreserveVolume) { const Vec3d InvLhsGVol = InvLhsMatrix * VolGradConstraint; const double GVolInvLhsGVol = VolGradConstraint.DotProduct(InvLhsGVol); // Check that the constraint can be satisfied. if (FMath::Abs(GVolInvLhsGVol) > Threshold) { const double LagrangeMultiplier = (1. / GVolInvLhsGVol) * (VolDistConstraint + VolGradConstraint.DotProduct(OutPosition)); const Vec3d VolumeCorrection = -LagrangeMultiplier * InvLhsGVol; OutPosition += VolumeImportance * VolumeCorrection; } } } return bSucces; } } }