/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2004-2016 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * * All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * * for more details. * * * ****************************************************************************/ #ifndef __VCG_IMPLICIT_TETRA_SMOOTHER #define __VCG_IMPLICIT_TETRA_SMOOTHER #include #include #include #include #define PENALTY 10000 namespace vcg { template class ImplicitTetraSmoother { typedef typename MeshType::FaceType FaceType; typedef typename MeshType::VertexType VertexType; typedef typename MeshType::TetraType TetraType; typedef typename MeshType::CoordType CoordType; typedef typename MeshType::ScalarType ScalarType; typedef typename Eigen::Matrix MatrixXm; public: struct FaceConstraint { int numF; std::vector BarycentricW; CoordType TargetPos; FaceConstraint() { numF = -1; } FaceConstraint(int _numF, const std::vector &_BarycentricW, const CoordType &_TargetPos) { numF = _numF; BarycentricW = std::vector(_BarycentricW.begin(), _BarycentricW.end()); TargetPos = _TargetPos; } }; struct Parameter { //the amount of smoothness, useful only if we set the mass matrix ScalarType lambda; //the use of mass matrix to keep the mesh close to its original position //(weighted per area distributed on vertices) bool useMassMatrix; //this bool is used to fix the border vertices of the mesh or not bool fixBorder; //this bool is used to set if cotangent weight is used, this flag to false means uniform laplacian bool useCotWeight; //use this weight for the laplacian when the cotangent one is not used ScalarType lapWeight; //the set of fixed vertices std::vector FixedV; //the set of faces for barycentric constraints std::vector ConstrainedF; //the degree of laplacian int degree; //this is to say if we smooth the positions or the quality bool SmoothQ; Parameter() { degree = 2; lambda = 0.05; useMassMatrix = true; fixBorder = true; useCotWeight = false; lapWeight = 1; SmoothQ = false; } }; private: static void InitSparse(const std::vector> &Index, const std::vector &Values, const int m, const int n, Eigen::SparseMatrix &X) { assert(Index.size() == Values.size()); std::vector> IJV; IJV.reserve(Index.size()); for (size_t i = 0; i < Index.size(); i++) { int row = Index[i].first; int col = Index[i].second; ScalarType val = Values[i]; assert(row < m); assert(col < n); IJV.push_back(Eigen::Triplet(row, col, val)); } X.resize(m, n); X.setFromTriplets(IJV.begin(), IJV.end()); } static void CollectHardConstraints(MeshType &mesh, const Parameter &SParam, std::vector> &IndexC, std::vector &WeightC, bool SmoothQ = false) { std::vector To_Fix; //collect fixed vert if (SParam.fixBorder) { //add penalization constra for (size_t i = 0; i < mesh.vert.size(); i++) { if (!mesh.vert[i].IsB()) continue; To_Fix.push_back(i); } } //add additional fixed vertices constraint To_Fix.insert(To_Fix.end(), SParam.FixedV.begin(), SParam.FixedV.end()); //sort and make them unique std::sort(To_Fix.begin(), To_Fix.end()); typename std::vector::iterator it = std::unique(To_Fix.begin(), To_Fix.end()); To_Fix.resize(std::distance(To_Fix.begin(), it)); for (size_t i = 0; i < To_Fix.size(); i++) { if (!SmoothQ) { for (int j = 0; j < 3; j++) { int IndexV = (To_Fix[i] * 3) + j; IndexC.push_back(std::pair(IndexV, IndexV)); WeightC.push_back((ScalarType)PENALTY); } } else { int IndexV = To_Fix[i]; IndexC.push_back(std::pair(IndexV, IndexV)); WeightC.push_back((ScalarType)PENALTY); } } } static void CollectBarycentricConstraints(MeshType &mesh, const Parameter &SParam, std::vector> &IndexC, std::vector &WeightC, std::vector &IndexRhs, std::vector &ValueRhs) { ScalarType penalty; int baseIndex = mesh.vert.size(); for (size_t i = 0; i < SParam.ConstrainedF.size(); i++) { //get the index of the current constraint int IndexConstraint = baseIndex + i; //add one hard constraint int FaceN = SParam.ConstrainedF[i].numF; assert(FaceN >= 0); assert(FaceN < (int)mesh.face.size()); assert(mesh.face[FaceN].VN() == (int)SParam.ConstrainedF[i].BarycentricW.size()); penalty = ScalarType(1) - SParam.lapWeight; assert(penalty > ScalarType(0) && penalty < ScalarType(1)); //then add all the weights to impose the constraint for (int j = 0; j < mesh.face[FaceN].VN(); j++) { //get the current weight ScalarType currW = SParam.ConstrainedF[i].BarycentricW[j]; //get the index of the current vertex int FaceVert = vcg::tri::Index(mesh, mesh.face[FaceN].V(j)); //then add the constraints componentwise for (int k = 0; k < 3; k++) { //multiply times 3 per component int IndexV = (FaceVert * 3) + k; //get the index of the current constraint int ComponentConstraint = (IndexConstraint * 3) + k; IndexC.push_back(std::pair(ComponentConstraint, IndexV)); WeightC.push_back(currW * penalty); IndexC.push_back(std::pair(IndexV, ComponentConstraint)); WeightC.push_back(currW * penalty); //this to avoid the 1 on diagonal last entry of mass matrix IndexC.push_back(std::pair(ComponentConstraint, ComponentConstraint)); WeightC.push_back(-1); } } for (int j = 0; j < 3; j++) { //get the index of the current constraint int ComponentConstraint = (IndexConstraint * 3) + j; //get per component value ScalarType ComponentV = SParam.ConstrainedF[i].TargetPos.V(j); //add the diagonal value IndexRhs.push_back(ComponentConstraint); ValueRhs.push_back(ComponentV * penalty); } } } static void MassMatrixEntry(MeshType &m, std::vector> &index, std::vector &entry, bool vertexCoord = true) { tri::RequireCompactness(m); typename MeshType::template PerVertexAttributeHandle h = tri::Allocator::template GetPerVertexAttribute(m, "volume"); for (int i = 0; i < m.vn; ++i) h[i] = 0; ForEachTetra(m, [&](TetraType &t) { ScalarType v = Tetra::ComputeVolume(t); for (int i = 0; i < 4; ++i) h[tri::Index(m, t.V(i))] += v; }); ScalarType maxV = 0; for (int i = 0; i < m.vn; ++i) maxV = max(maxV, h[i]); for (int i = 0; i < m.vn; ++i) { int currI = i; index.push_back(std::pair(currI, currI)); entry.push_back(h[i] / maxV); } tri::Allocator::template DeletePerVertexAttribute(m, h); } static ScalarType ComputeCotangentWeight(TetraType &t, const int i) { //i is the edge in the tetra tetra::Pos pp(&t, Tetra::FofE(i, 0), i, Tetra::VofE(i, 0)); tetra::Pos pt(&t, Tetra::FofE(i, 0), i, Tetra::VofE(i, 0)); ScalarType weight = 0; do { CoordType po0 = t.V(Tetra::VofE(5 - pt.E(), 0))->cP(); CoordType po1 = t.V(Tetra::VofE(5 - pt.E(), 1))->cP(); ScalarType length = vcg::Distance(po0, po1); ScalarType cot = std::tan((M_PI / 2.) - Tetra::DihedralAngle(*pt.T(), 5 - pt.E())); weight = (length / 6.) * cot; pt.FlipT(); pt.FlipF(); } while (pp != pt); return weight; } static void GetLaplacianEntry(MeshType &mesh, TetraType &t, std::vector> &index, std::vector &entry, bool cotangent, ScalarType weight = 1, bool vertexCoord = true) { // if (cotangent) // vcg::tri::MeshAssert::OnlyT(mesh); //iterate on edges for (int i = 0; i < 6; ++i) { weight = 1;//ComputeCotangentWeight(t, i); int indexV0 = Index(mesh, t.V(Tetra::VofE(i, 0))); int indexV1 = Index(mesh, t.V(Tetra::VofE(i, 1))); for (int j = 0; j < 3; j++) { //multiply by 3 and add the component int currI0 = (indexV0 * 3) + j; int currI1 = (indexV1 * 3) + j; index.push_back(std::pair(currI0, currI0)); entry.push_back(weight); index.push_back(std::pair(currI0, currI1)); entry.push_back(-weight); index.push_back(std::pair(currI1, currI1)); entry.push_back(weight); index.push_back(std::pair(currI1, currI0)); entry.push_back(-weight); } } } static void GetLaplacianMatrix(MeshType &mesh, std::vector> &index, std::vector &entry, bool cotangent, ScalarType weight = 1, bool vertexCoord = true) { //store the index and the scalar for the sparse matrix ForEachTetra(mesh, [&](TetraType &t) { GetLaplacianEntry(mesh, t, index, entry, cotangent, weight); }); } public: static void Compute(MeshType &mesh, Parameter &SParam) { //calculate the size of the system int matr_size = mesh.vert.size() + SParam.ConstrainedF.size(); //the laplacian and the mass matrix Eigen::SparseMatrix L, M, B; //initialize the mass matrix std::vector> IndexM; std::vector ValuesM; //add the entries for mass matrix if (SParam.useMassMatrix) MassMatrixEntry(mesh, IndexM, ValuesM, !SParam.SmoothQ); //then add entries for lagrange mult due to barycentric constraints for (size_t i = 0; i < SParam.ConstrainedF.size(); i++) { int baseIndex = (mesh.vert.size() + i) * 3; if (SParam.SmoothQ) baseIndex = (mesh.vert.size() + i); if (SParam.SmoothQ) { IndexM.push_back(std::pair(baseIndex, baseIndex)); ValuesM.push_back(1); } else { for (int j = 0; j < 3; j++) { IndexM.push_back(std::pair(baseIndex + j, baseIndex + j)); ValuesM.push_back(1); } } } //add the hard constraints CollectHardConstraints(mesh, SParam, IndexM, ValuesM, SParam.SmoothQ); //initialize sparse mass matrix if (!SParam.SmoothQ) InitSparse(IndexM, ValuesM, matr_size * 3, matr_size * 3, M); else InitSparse(IndexM, ValuesM, matr_size, matr_size, M); //initialize the barycentric matrix std::vector> IndexB; std::vector ValuesB; std::vector IndexRhs; std::vector ValuesRhs; //then also collect hard constraints if (!SParam.SmoothQ) { CollectBarycentricConstraints(mesh, SParam, IndexB, ValuesB, IndexRhs, ValuesRhs); //initialize sparse constraint matrix InitSparse(IndexB, ValuesB, matr_size * 3, matr_size * 3, B); } else InitSparse(IndexB, ValuesB, matr_size, matr_size, B); //get the entries for laplacian matrix std::vector> IndexL; std::vector ValuesL; GetLaplacianMatrix(mesh, IndexL, ValuesL, SParam.useCotWeight, SParam.lapWeight, !SParam.SmoothQ); //initialize sparse laplacian matrix if (!SParam.SmoothQ) InitSparse(IndexL, ValuesL, matr_size * 3, matr_size * 3, L); else InitSparse(IndexL, ValuesL, matr_size, matr_size, L); for (int i = 0; i < (SParam.degree - 1); i++) L = L * L; //then solve the system Eigen::SparseMatrix S = (M + B + SParam.lambda * L); //SimplicialLDLT Eigen::SimplicialCholesky> solver(S); assert(solver.info() == Eigen::Success); MatrixXm V; if (!SParam.SmoothQ) V = MatrixXm(matr_size * 3, 1); else V = MatrixXm(matr_size, 1); //set the first part of the matrix with vertex values if (!SParam.SmoothQ) { for (size_t i = 0; i < mesh.vert.size(); i++) { int index = i * 3; V(index, 0) = mesh.vert[i].P().X(); V(index + 1, 0) = mesh.vert[i].P().Y(); V(index + 2, 0) = mesh.vert[i].P().Z(); } } else { for (size_t i = 0; i < mesh.vert.size(); i++) { int index = i; V(index, 0) = mesh.vert[i].Q(); } } //then set the second part by considering RHS gien by barycentric constraint for (size_t i = 0; i < IndexRhs.size(); i++) { int index = IndexRhs[i]; ScalarType val = ValuesRhs[i]; V(index, 0) = val; } //solve the system V = solver.solve(M * V).eval(); //then copy back values if (!SParam.SmoothQ) { for (size_t i = 0; i < mesh.vert.size(); i++) { int index = i * 3; mesh.vert[i].P().X() = V(index, 0); mesh.vert[i].P().Y() = V(index + 1, 0); mesh.vert[i].P().Z() = V(index + 2, 0); } } else { for (size_t i = 0; i < mesh.vert.size(); i++) { int index = i; mesh.vert[i].Q() = V(index, 0); } } } }; } //end namespace vcg #endif