/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2004-2017 \/)\/ * * 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_ISOTROPICREMESHING_H #define _VCG_ISOTROPICREMESHING_H #include #include #include #include #include #include #include #include #include #include #include #include namespace vcg { namespace tri { template class IsotropicRemeshing { public: typedef TRI_MESH_TYPE MeshType; typedef typename MeshType::FaceType FaceType; typedef typename MeshType::FacePointer FacePointer; typedef typename FaceType::VertexType VertexType; typedef typename FaceType::VertexPointer VertexPointer; typedef typename VertexType::ScalarType ScalarType; typedef typename VertexType::CoordType CoordType; typedef typename face::Pos PosType; typedef BasicVertexPair VertexPair; typedef EdgeCollapser Collapser; typedef GridStaticPtr StaticGrid; typedef struct Params { typedef struct Stat { int splitNum; int collapseNum; int flipNum; void Reset() { splitNum=0; collapseNum=0; flipNum=0; } } Stat; ScalarType minLength; // minimal admitted length: no edge should be shorter than this value (used when collapsing) ScalarType maxLength; // maximal admitted length: no edge should be longer than this value (used when refining) ScalarType lengthThr; ScalarType minAdaptiveMult = 1; ScalarType maxAdaptiveMult = 1; ScalarType minimalAdmittedArea; ScalarType maxSurfDist; ScalarType aspectRatioThr = 0.05; //min aspect ratio: during relax bad triangles will be relaxed ScalarType foldAngleCosThr = cos(math::ToRad(140.)); //min angle to be considered folding: during relax folded triangles will be relaxed ScalarType creaseAngleRadThr = math::ToRad(10.0); ScalarType creaseAngleCosThr = cos(math::ToRad(10.0)); //min angle to be considered crease: two faces with normals diverging more than thr share a crease edge bool splitFlag = true; bool swapFlag = true; bool collapseFlag = true; bool smoothFlag=true; bool projectFlag=true; bool selectedOnly = false; bool cleanFlag = true; bool userSelectedCreases = false; bool surfDistCheck = true; bool adapt=false; int iter=1; Stat stat; void SetTargetLen(const ScalarType len) { minLength=len*4./5.; maxLength=len*4./3.; lengthThr=len*4./3.; minimalAdmittedArea = (minLength * minLength)/1000.0; } void SetFeatureAngleDeg(const ScalarType angle) { creaseAngleRadThr = math::ToRad(angle); creaseAngleCosThr = cos(creaseAngleRadThr); } StaticGrid grid; MeshType* m; MeshType* mProject; } Params; private: static void debug_crease (MeshType & toRemesh, std::string prepend, int i) { ForEachVertex(toRemesh, [] (VertexType & v) { v.C() = Color4b::Gray; v.Q() = 0; }); ForEachFacePos(toRemesh, [&](PosType &p){ if (p.F()->IsFaceEdgeS(p.E())) { p.V()->Q() += 1; p.VFlip()->Q() += 1; } }); ForEachVertex(toRemesh, [] (VertexType & v) { if (v.Q() >= 4) v.C() = Color4b::Green; else if (v.Q() >= 2) v.C() = Color4b::Red; }); prepend += "_creases" + std::to_string(i) + ".ply"; vcg::tri::io::Exporter::Save(toRemesh, prepend.c_str(), vcg::tri::io::Mask::IOM_ALL); } static void removeColinearFaces(MeshType & m, Params & params) { vcg::tri::UpdateTopology::FaceFace(m); int count = 0; int iter = 0; do { // vcg::tri::UpdateTopology::FaceFace(m); vcg::tri::UnMarkAll(m); count = 0; for (size_t i = 0; i < size_t(m.FN()); ++i) { FaceType & f = m.face[i]; const ScalarType quality = vcg::QualityRadii(f.cP(0), f.cP(1), f.cP(2)); if (quality <= 0.001) { //find longest edge const double edges[3] = { vcg::Distance(f.cP(0), f.cP(1)), vcg::Distance(f.cP(1), f.cP(2)), vcg::Distance(f.cP(2), f.cP(0)) }; const double smallestEdge = std::min(edges[0], std::min(edges[1], edges[2])); const int longestIdx = int(std::find(edges, edges+3, std::max(std::max(edges[0], edges[1]), edges[2])) - (edges)); if (vcg::tri::IsMarked(m, f.V2(longestIdx))) continue; auto f1 = f.cFFp(longestIdx); vcg::tri::Mark(m, f.V2(longestIdx)); if (!vcg::face::IsBorder(f, longestIdx) && vcg::face::IsManifold(f, longestIdx) && vcg::face::checkFlipEdgeNotManifold(f, longestIdx)) { // Check if EdgeFlipping improves quality const FacePointer g = f.FFp(longestIdx); const int k = f.FFi(longestIdx); const vcg::Triangle3 t1(f.P(longestIdx), f.P1(longestIdx), f.P2(longestIdx)); const vcg::Triangle3 t2(g->P(k), g->P1(k), g->P2(k)); const vcg::Triangle3 t3(f.P(longestIdx), g->P2(k), f.P2(longestIdx)); const vcg::Triangle3 t4(g->P(k), f.P2(longestIdx), g->P2(k)); const auto n1 = vcg::TriangleNormal(t1); const auto n2 = vcg::TriangleNormal(t2); const auto n3 = vcg::TriangleNormal(t3); const auto n4 = vcg::TriangleNormal(t4); const auto biggestSmallest = vcg::DoubleArea(t1) > vcg::DoubleArea(t2) ? std::make_pair(t1, t2) : std::make_pair(t2, t1); const auto areaRatio = vcg::DoubleArea(biggestSmallest.first) / vcg::DoubleArea(biggestSmallest.second); bool normalCheck = true; // if (n1.Norm() > 0.001 && n2.Norm() > 0.001) { const auto referenceNormal = vcg::NormalizedTriangleNormal(biggestSmallest.first); normalCheck &= vcg::NormalizedTriangleNormal(t3) * referenceNormal >= 0.95; normalCheck &= vcg::NormalizedTriangleNormal(t4) * referenceNormal >= 0.95; } bool areaCheck = false; if (areaRatio > 1000) { areaCheck |= vcg::DoubleArea(t3) / vcg::DoubleArea(biggestSmallest.second) > 1000 && vcg::DoubleArea(t4) / vcg::DoubleArea(biggestSmallest.second) > 1000; } if ((normalCheck) && (areaCheck || std::min( QualityFace(t1), QualityFace(t2) ) <= std::min( QualityFace(t3), QualityFace(t4)))) { ScalarType dist; CoordType closest; auto fp0 = vcg::tri::GetClosestFaceBase(*params.mProject, params.grid, vcg::Barycenter(t3), smallestEdge/4., dist, closest); if (fp0 == NULL) continue; auto fp1 = vcg::tri::GetClosestFaceBase(*params.mProject, params.grid, vcg::Barycenter(t4), smallestEdge/4., dist, closest); if (fp1 == NULL) continue; vcg::face::FlipEdgeNotManifold(f, longestIdx); ++count; } } } } } while (count && ++iter < 75); } static void cleanMesh(MeshType & m, Params & params) { vcg::tri::Clean::RemoveDuplicateFace(m); vcg::tri::Clean::RemoveUnreferencedVertex(m); vcg::tri::Allocator::CompactEveryVector(m); // vcg::tri::UpdateTopology::FaceFace(m); removeColinearFaces(m, params); // vcg::tri::UpdateTopology::FaceFace(m); } public: static void Do(MeshType &toRemesh, Params & params, vcg::CallBackPos * cb=0) { MeshType toProjectCopy; tri::UpdateBounding::Box(toRemesh); tri::UpdateNormal::PerVertexNormalizedPerFaceNormalized(toRemesh); tri::Append::MeshCopy(toProjectCopy, toRemesh); Do(toRemesh,toProjectCopy,params,cb); } static void Do(MeshType &toRemesh, MeshType &toProject, Params & params, vcg::CallBackPos * cb=0) { assert(&toRemesh != &toProject); params.stat.Reset(); tri::UpdateBounding::Box(toRemesh); { tri::UpdateBounding::Box(toProject); tri::UpdateNormal::PerFaceNormalized(toProject); params.m = &toRemesh; params.mProject = &toProject; params.grid.Set(toProject.face.begin(), toProject.face.end()); } if (params.cleanFlag) cleanMesh(toRemesh, params); tri::UpdateTopology::FaceFace(toRemesh); tri::UpdateFlags::VertexBorderFromFaceAdj(toRemesh); tri::UpdateTopology::VertexFace(toRemesh); if (!params.userSelectedCreases) tagCreaseEdges(toRemesh, params); for(int i=0; i < params.iter; ++i) { if(cb) cb(100*i/params.iter, "Remeshing"); if (params.adapt) { computeQualityDistFromRadii(toRemesh); vcg::tri::Smooth::VertexQualityLaplacian(toRemesh, 2); } if(params.splitFlag) SplitLongEdges(toRemesh, params); #ifdef DEBUG_CREASE debug_crease(toRemesh, std::string("after_ref"), i); #endif if(params.collapseFlag) { CollapseShortEdges(toRemesh, params); CollapseCrosses(toRemesh, params); } if(params.swapFlag) ImproveValence(toRemesh, params); if(params.smoothFlag) ImproveByLaplacian(toRemesh, params); if(params.projectFlag) ProjectToSurface(toRemesh, params); } } static int tagCreaseEdges(MeshType &m, Params & params, bool forceTag = false) { int count = 0; std::vector creaseVerts(m.VN(), 0); vcg::tri::UpdateFlags::VertexClearV(m); std::queue creaseQueue; //if we are forcing the crease taggin or we are not using user creases...reset the faceedgeS... if ((forceTag || !params.userSelectedCreases)) vcg::tri::UpdateFlags::FaceClearFaceEdgeS(m); ForEachFacePos(m, [&](PosType &p){ if (p.IsBorder()) p.F()->SetFaceEdgeS(p.E()); const FaceType *ff = p.F(); const FaceType *ffAdj = p.FFlip(); const double quality = vcg::QualityRadii(ff->cP(0), ff->cP(1), ff->cP(2)); const double qualityAdj = vcg::QualityRadii(ffAdj->cP(0), ffAdj->cP(1), ffAdj->cP(2)); const bool qualityCheck = quality > 0.00000001 && qualityAdj > 0.00000001; // bool areaCheck = vcg::DoubleArea(*ff) > 0.000001 && vcg::DoubleArea(*ffAdj) > 0.000001; if ((forceTag || !params.userSelectedCreases) && (testCreaseEdge(p, params.creaseAngleCosThr) /*&& areaCheck*/ /* && qualityCheck*/) || p.IsBorder()) { PosType pp = p; std::vector faces; std::vector edges; bool allOk = true; do { faces.push_back(pp.F()); edges.push_back(pp.E()); // pp.F()->SetFaceEdgeS(pp.E()); if (vcg::QualityRadii(pp.F()->cP(0), pp.F()->cP(1), pp.F()->cP(2)) <= 0.0001) { allOk = false; break; } pp.NextF(); } while (pp != p); if (allOk) { for (int i = 0; i < faces.size(); ++i) { faces[i]->SetFaceEdgeS(edges[i]); } } creaseQueue.push(p); } }); return count; } private: /* TODO: Add better crease support: detect all creases at starting time, saving it on facedgesel flags All operations must then preserve the faceedgesel flag accordingly: Refinement -> Check that refiner propagates faceedgesel [should be doing it] Collapse -> Implement 1D edge collapse and better check on corners and creases Swap -> Totally avoid swapping crease edges [ok] Smooth -> Apply 1D smoothing to crease vertices + check on (http://www.cs.ubc.ca/labs/imager/tr/2009/eltopo/sisc2009.pdf) */ IsotropicRemeshing() {} // this returns the value of cos(a) where a is the angle between n0 and n1. (scalar prod is cos(a)) static inline ScalarType fastAngle(const Point3 & n0, const Point3 & n1) { return math::Clamp(n0*n1,(ScalarType)-1.0,(ScalarType)1.0); } // compare the value of the scalar prod with the cos of the crease threshold static inline bool testCreaseEdge(PosType &p, const ScalarType creaseCosineThr) { const ScalarType angle = fastAngle(NormalizedTriangleNormal(*(p.F())), NormalizedTriangleNormal(*(p.FFlip()))); return angle <= creaseCosineThr && angle >= -0.98; // return (angle <= creaseCosineThr && angle >= -creaseCosineThr); } // this stores in minQ the value of the 10th percentile of the VertQuality distribution and in // maxQ the value of the 90th percentile. static inline void computeVQualityDistrMinMax(MeshType &m, ScalarType &minQ, ScalarType &maxQ) { Distribution distr; tri::Stat::ComputePerVertexQualityDistribution(m,distr); maxQ = distr.Percentile(0.9f); minQ = distr.Percentile(0.1f); } static inline ScalarType computeLengthThrMult(const Params & params, const ScalarType & quality) { return (params.adapt) ? math::ClampedLerp(params.minAdaptiveMult, params.maxAdaptiveMult, quality) : (ScalarType) 1; } //Computes PerVertexQuality as a function of the 'deviation' of the normals taken from //the faces incident to each vertex static void computeQuality(MeshType &m) { tri::RequirePerVertexQuality(m); tri::UpdateFlags::VertexClearV(m); for(auto vi=m.vert.begin(); vi!=m.vert.end(); ++vi) if(!(*vi).IsD()) { std::vector ff; face::VFExtendedStarVF(&*vi, 2, ff); assert(ff.size() > 0); const Point3 & fNormal = NormalizedTriangleNormal(**it); const auto tot = std::accumulate(++ff.begin(), ff.end(), 0.d, [&](const ScalarType acc, const FaceType * f) { return acc + (1 - math::Abs(fastAngle(n, NormalizedTriangleNormal(*f)))); }); vi->Q() = tot / (std::max(1, ((int)ff.size()-1))); vi->SetV(); } tri::Smooth::VertexQualityLaplacian(m, 3); } static void computeQualityDistFromCrease(MeshType & m) { tri::RequirePerVertexQuality(m); tri::UpdateTopology::FaceFace(m); // tri::UpdateFlags::VertexClearV(m); ForEachVertex(m, [&] (VertexType & v) { v.IMark() = 0; }); std::vector seeds; ForEachFace(m, [&] (FaceType & f) { for (int i = 0; i < 3; ++i) { if (f.IsFaceEdgeS(i)) { seeds.push_back(f.V0(i)); seeds.push_back(f.V1(i)); } } }); std::sort(seeds.begin(),seeds.end()); auto last=std::unique(seeds.begin(),seeds.end()); seeds.erase(last, seeds.end()); tri::EuclideanDistance eu; tri::Geodesic::PerVertexDijkstraCompute(m, seeds, eu); tri::Smooth::VertexQualityLaplacian(m, 2); ForEachVertex(m, [] (VertexType & v) { v.Q() = 1 / (v.Q() + 1); }); } static void computeQualityDistFromRadii(MeshType & m) { tri::RequirePerVertexQuality(m); tri::RequirePerFaceQuality(m); ScalarType maxV = std::numeric_limits::lowest(); ScalarType minV = std::numeric_limits::max(); ForEachFace(m, [&] (FaceType & f) { f.Q() = 1. - vcg::QualityRadii(f.cP(0), f.cP(1), f.cP(2)); maxV = std::max(maxV, f.Q()); minV = std::min(minV, f.Q()); }); vcg::tri::UpdateQuality::VertexFromFace(m); maxV = std::numeric_limits::lowest(); minV = std::numeric_limits::max(); //normalize in [0,1] with square reshape ForEachVertex(m, [&] (const VertexType & v) { maxV = std::max(maxV, v.Q()); minV = std::min(minV, v.Q()); }); const ScalarType vRange = maxV - minV + 0.000001; ForEachVertex(m, [&] (VertexType & v) { v.Q() = std::pow((v.Q() - minV) / vRange, 2.); }); } static void computeQualityDistFromHeight(MeshType & m, const ScalarType lowerBound, const ScalarType higherBound) { tri::RequirePerVertexQuality(m); tri::RequirePerFaceQuality(m); ForEachFace(m, [&] (FaceType & f) { ScalarType minH = std::numeric_limits::max(); for (int i = 0; i < 3; ++i) { CoordType closest; ScalarType dist = 0; vcg::Segment3 seg(f.cP1(i), f.cP2(i)); vcg::SegmentPointDistance(seg, f.cP0(i), closest, dist); minH = std::min(minH, dist); } f.Q() = math::Clamp(minH, lowerBound, higherBound); }); } /* Computes the ideal valence for the vertex in pos p: 4 for border vertices 6 for internal vertices */ static inline int idealValence(const PosType &p) { if(p.IsBorder()) return 4; return 6; } static inline int idealValence(const VertexType &v) { if(v.IsB()) return 4; return 6; } static inline int idealValenceSlow(const PosType &p) { std::vector posVec; VFOrderedStarFF(p,posVec); const auto angleSumRad = std::accumulate(posVec.begin, posVec.end(), 0, [](const ScalarType acc, const PosType & p) { return acc + p.AngleRad(); }); return (int)(std::ceil(angleSumRad / (M_PI/3.0f))); } static bool testHausdorff (MeshType & m, StaticGrid & grid, const std::vector & verts, const ScalarType maxD, const CoordType & checkOrientation = CoordType(0,0,0)) { for (CoordType v : verts) { CoordType closest, normal, ip; ScalarType dist = 0; const FaceType* fp = GetClosestFaceBase(m, grid, v, maxD, dist, closest); //you can't use this kind of orientation check, since when you stand on edges it fails if (fp == NULL || (checkOrientation != CoordType(0,0,0) && checkOrientation * fp->N() < 0.7)) { return false; } } return true; } /* Edge Swap Step: This method optimizes the valence of each vertex. oldDist is the sum of the absolute distance of each vertex from its ideal valence newDist is the sum of the absolute distance of each vertex from its ideal valence after the edge swap. If the swap decreases the total absolute distance, then it's applied, preserving the triangle quality. +1 v1 v1 / \ /|\ / \ / | \ / \ / | \ / _*p\ -1/ | \ -1 v2--------v0 ========> v2 | v0 \ / \ | / \ / \ | / \ / \ | / \ / \|/ +1 v3 v3 Before Swap After Swap */ //TODO: check if you can optimize using posType valence counting functions static bool testSwap(const PosType & p, const ScalarType creaseAngleCosThr) { //if border or feature, do not swap if (/*p.IsBorder() || */p.IsEdgeS()) return false; int oldDist = 0, newDist = 0, idealV = 0, actualV = 0; PosType tp=p; const VertexType *v0=tp.V(); std::vector incident; // vcg::face::VVStarVF(tp.V(), incident); idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices();//int(incident.size()); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV - 1)); tp.NextF();tp.FlipE();tp.FlipV(); const VertexType *v1=tp.V(); // vcg::face::VVStarVF(tp.V(), incident); idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices();//int(incident.size()); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV + 1)); tp.FlipE();tp.FlipV();tp.FlipE(); const VertexType *v2=tp.V(); // vcg::face::VVStarVF(tp.V(), incident); idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices();//int(incident.size()); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV - 1)); tp.NextF();tp.FlipE();tp.FlipV(); const VertexType *v3=tp.V(); // vcg::face::VVStarVF(tp.V(), incident); idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices();//int(incident.size()); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV + 1)); const ScalarType qOld = std::min(Quality(v0->P(),v2->P(),v3->P()),Quality(v0->P(),v1->P(),v2->P())); const ScalarType qNew = std::min(Quality(v0->P(),v1->P(),v3->P()),Quality(v2->P(),v3->P(),v1->P())); return (newDist < oldDist && qNew >= qOld * 0.50f) || (newDist == oldDist && qNew > qOld * 1.f) || qNew > 1.5f * qOld; } static bool checkManifoldness(const FaceType & f, const int z) { PosType pos(&f, (z+2)%3, f.V2(z)); const PosType start = pos; do { pos.FlipE(); if (!face::IsManifold(*pos.F(), pos.E())) break; pos.FlipF(); } while (pos!=start); return pos == start; } // Edge swap step: edges are flipped in order to optimize valence and triangle quality across the mesh static void ImproveValence(MeshType &m, Params ¶ms) { const static ScalarType foldCheckRad = math::ToRad(5.); tri::UpdateTopology::FaceFace(m); tri::UpdateTopology::VertexFace(m); ForEachFace(m, [&] (FaceType & f) { // if (face::IsManifold(f, 0) && face::IsManifold(f, 1) && face::IsManifold(f, 2)) for (int i = 0; i < 3; ++i) { if (&f > f.cFFp(i)) { const PosType pi(&f, i); const CoordType swapEdgeMidPoint = (f.cP2(i) + f.cFFp(i)->cP2(f.cFFi(i))) / 2.; if(((!params.selectedOnly) || (f.IsS() && f.cFFp(i)->IsS())) && !face::IsBorder(f, i) && face::IsManifold(f, i) && /*checkManifoldness(f, i) &&*/ face::checkFlipEdgeNotManifold(f, i) && testSwap(pi, params.creaseAngleCosThr) && // face::CheckFlipEdge(f, i) && face::CheckFlipEdgeNormal(f, i, float(vcg::math::ToRad(5.))) && (!params.surfDistCheck || testHausdorff(*params.mProject, params.grid, { swapEdgeMidPoint }, params.maxSurfDist))) { //When doing the swap we need to preserve and update the crease info accordingly FaceType* g = f.cFFp(i); const int w = f.FFi(i); const bool creaseF = g->IsFaceEdgeS((w + 1) % 3); const bool creaseG = f.IsFaceEdgeS((i + 1) % 3); face::FlipEdgeNotManifold(f, i); f.ClearFaceEdgeS((i + 1) % 3); g->ClearFaceEdgeS((w + 1) % 3); if (creaseF) f.SetFaceEdgeS(i); if (creaseG) g->SetFaceEdgeS(w); ++params.stat.flipNum; break; } } } }); } // The predicate that defines which edges should be split class EdgeSplitAdaptPred { public: int count = 0; ScalarType length, lengthThr, minQ, maxQ; const Params & params; EdgeSplitAdaptPred(const Params & p) : params(p) {}; bool operator()(PosType &ep) { const ScalarType quality = ((ep.V()->Q()+ ep.VFlip()->Q())/(ScalarType)2.0); const ScalarType mult = computeLengthThrMult(params, quality); const ScalarType dist = Distance(ep.V()->P(), ep.VFlip()->P()); if(dist > mult * length) { ++count; return true; } else return false; } }; class EdgeSplitLenPred { public: int count = 0; ScalarType squaredlengthThr; bool operator()(PosType &ep) { if(SquaredDistance(ep.V()->P(), ep.VFlip()->P()) > squaredlengthThr) { ++count; return true; } else return false; } }; //Split pass: This pass uses the tri::RefineE from the vcglib to implement //the refinement step, using EdgeSplitPred as a predicate to decide whether to split or not static void SplitLongEdges(MeshType &m, Params ¶ms) { tri::UpdateTopology::FaceFace(m); tri::MidPoint midFunctor(&m); ScalarType minQ = 0, maxQ = 0; if(params.adapt){ computeVQualityDistrMinMax(m, minQ, maxQ); EdgeSplitAdaptPred ep(params); ep.minQ = minQ; ep.maxQ = maxQ; ep.length = params.maxLength; ep.lengthThr = params.lengthThr; tri::RefineMidpoint(m, ep, params.selectedOnly); params.stat.splitNum+=ep.count; } else { EdgeSplitLenPred ep; ep.squaredlengthThr = params.maxLength*params.maxLength; tri::RefineMidpoint(m, ep, params.selectedOnly); params.stat.splitNum+=ep.count; } } static int VtoE(const int v0, const int v1) { static constexpr int Vmat[3][3] = { -1, 0, 2, 0, -1, 1, 2, 1, -1}; return Vmat[v0][v1]; } static bool checkCanMoveOnCollapse(const PosType & p, const std::vector & faces, const std::vector & vIdxes, const Params ¶ms) { bool allIncidentFaceSelected = true; PosType pi = p; const CoordType dEdgeVector = (p.V()->cP() - p.VFlip()->cP()).Normalize(); int incidentFeatures = 0; vcg::tri::UnMarkAll(*params.m); for (size_t i = 0; i < faces.size(); ++i) { if (faces[i]->IsFaceEdgeS(VtoE(vIdxes[i], (vIdxes[i]+1)%3)) && !vcg::tri::IsMarked(*params.m, faces[i]->cV1(vIdxes[i]))) { vcg::tri::Mark(*params.m,faces[i]->V1(vIdxes[i])); incidentFeatures++; const CoordType movingEdgeVector0 = (faces[i]->cP1(vIdxes[i]) - faces[i]->cP(vIdxes[i])).Normalize(); if (std::fabs(movingEdgeVector0 * dEdgeVector) < .9f || !p.IsEdgeS()) return false; } if (faces[i]->IsFaceEdgeS(VtoE(vIdxes[i], (vIdxes[i]+2)%3)) && !vcg::tri::IsMarked(*params.m, faces[i]->cV2(vIdxes[i]))) { vcg::tri::Mark(*params.m,faces[i]->V2(vIdxes[i])); incidentFeatures++; const CoordType movingEdgeVector1 = (faces[i]->cP2(vIdxes[i]) - faces[i]->cP(vIdxes[i])).Normalize(); if (std::fabs(movingEdgeVector1 * dEdgeVector) < .9f || !p.IsEdgeS()) return false; } allIncidentFaceSelected &= faces[i]->IsS(); } if (incidentFeatures > 2) return false; return params.selectedOnly ? allIncidentFaceSelected : true; } static bool checkFacesAfterCollapse (const std::vector & faces, const PosType & p, const Point3 &mp, Params ¶ms, bool relaxed) { for (FaceType* f : faces) { if(!(*f).IsD() && f != p.F()) //i'm not a deleted face { const PosType pi(f, p.V()); //same vertex const auto v0 = pi.V(); const auto v1 = pi.F()->V1(pi.VInd()); const auto v2 = pi.F()->V2(pi.VInd()); if( v1 == p.VFlip() || v2 == p.VFlip()) //i'm the other deleted face continue; //check on new face quality { const auto newQ = Quality(mp, v1->P(), v2->P()); const auto oldQ = Quality(v0->P(), v1->P(), v2->P()); if(newQ <= 0.5*oldQ) return false; } // we prevent collapse that makes edges too long (except for cross) if(!relaxed && (Distance(mp, v1->P()) > params.maxLength || Distance(mp, v2->P()) > params.maxLength)) return false; const auto oldN = NormalizedTriangleNormal(*(pi.F())); const auto newN = Normal(mp, v1->P(), v2->P()).Normalize(); if (oldN * newN < 0.7f) return false; //check on new face distance from original mesh if (params.surfDistCheck) { const auto points = { (v1->cP() + mp) / 2., (v2->cP() + mp) / 2., mp, }; if (!testHausdorff(*(params.mProject), params.grid, points, params.maxSurfDist) || !testHausdorff(*(params.mProject), params.grid, { (v1->cP() + v2->cP() + mp) / 3. }, params.maxSurfDist, newN)) return false; } } } return true; } //TODO: Refactor code and implement the correct set up of crease info when collapsing towards a crease edge static bool checkCollapseFacesAroundVert1(const PosType &p, VertexPair & pair, Point3 &mp, Params ¶ms, bool relaxed) { PosType p0 = p, p1 = p; p1.FlipV(); std::vector vi0, vi1; std::vector ff0, ff1; face::VFStarVF(p0.V(), ff0, vi0); face::VFStarVF(p1.V(), ff1, vi1); //check crease-moveability const bool moveable0 = checkCanMoveOnCollapse(p0, ff0, vi0, params) && !p0.V()->IsS(); const bool moveable1 = checkCanMoveOnCollapse(p1, ff1, vi1, params) && !p1.V()->IsS(); //if both moveable => go to midpoint // else collapse on movable one if (!moveable0 && !moveable1) return false; pair = moveable0 ? VertexPair(p0.V(), p1.V()) : VertexPair(p1.V(), p0.V()); mp = (p0.V()->cP() * int(moveable1) + p1.V()->cP() * int(moveable0)) / (int(moveable0) + int(moveable1)); if (checkFacesAfterCollapse(ff0, p0, mp, params, relaxed)) return checkFacesAfterCollapse(ff1, p1, mp, params, relaxed); return false; } static bool testCollapse1(const PosType &p, VertexPair & pair, Point3 &mp, ScalarType minQ, ScalarType maxQ, Params ¶ms, bool relaxed = false) { const ScalarType quality = params.adapt ? ((p.V()->Q()+ p.VFlip()->Q())/(ScalarType)2.0) : 0; const ScalarType mult = computeLengthThrMult(params, quality); const ScalarType thr = mult*params.minLength; const ScalarType dist = Distance(p.V()->P(), p.VFlip()->P()); const ScalarType area = DoubleArea(*(p.F()))/2.f; if(relaxed || (dist < thr || area < params.minLength*params.minLength/100.f))//if to collapse { return checkCollapseFacesAroundVert1(p, pair, mp, params, relaxed); } return false; } //This function is especially useful to enforce feature preservation during collapses //of boundary edges in planar or near planar section of the mesh static bool chooseBoundaryCollapse(PosType &p, VertexPair &pair) { Point3 collapseNV, collapsedNV0, collapsedNV1; collapseNV = (p.V()->P() - p.VFlip()->P()).normalized(); std::vector vv; face::VVStarVF(p.V(), vv); for(VertexType *v: vv) if(!(*v).IsD() && (*v).IsB() && v != p.VFlip()) //ignore non border collapsedNV0 = ((*v).P() - p.VFlip()->P()).normalized(); //edge vector after collapse face::VVStarVF(p.VFlip(), vv); for(VertexType *v: vv) if(!(*v).IsD() && (*v).IsB() && v != p.V()) //ignore non border collapsedNV1 = ((*v).P() - p.V()->P()).normalized(); //edge vector after collapse const float cosine = cos(math::ToRad(1.5f)); const float angle0 = fabs(fastAngle(collapseNV, collapsedNV0)); const float angle1 = fabs(fastAngle(collapseNV, collapsedNV1)); //if on both sides we deviate too much after collapse => don't collapse if(angle0 <= cosine && angle1 <= cosine) return false; //choose the best collapse (the more parallel one to the previous edge..) pair = (angle0 >= angle1) ? VertexPair(p.V(), p.VFlip()) : VertexPair(p.VFlip(), p.V()); return true; } //The actual collapse step: foreach edge it is collapse iff TestCollapse returns true AND // the linkConditions are preserved static void CollapseShortEdges(MeshType &m, Params ¶ms) { ScalarType minQ = 0, maxQ = 0; int candidates = 0; if(params.adapt) computeVQualityDistrMinMax(m, minQ, maxQ); tri::UpdateTopology::VertexFace(m); tri::UpdateFlags::FaceBorderFromVF(m); tri::UpdateFlags::VertexBorderFromFaceBorder(m); SelectionStack ss(m); ss.push(); { // tri::UpdateTopology::FaceFace(m); Clean::CountNonManifoldVertexFF(m,true); //FROM NOW ON VSelection is NotManifold ForEachFace(m, [&](FaceType &f) { if(!f.IsD() && (params.selectedOnly == false || f.IsS())) { for(auto i=0; i<3; ++i) { PosType pi(&f, i); ++candidates; VertexPair bp = VertexPair(pi.V(), pi.VFlip()); Point3 mp = (pi.V()->P()+pi.VFlip()->P())/2.f; if(testCollapse1(pi, bp, mp, minQ, maxQ, params) && Collapser::LinkConditions(bp)) { Collapser::Do(m, bp, mp, true); ++params.stat.collapseNum; break; } } } }); } ss.pop(); } //Here I just need to check the faces of the cross, since the other faces are not //affected by the collapse of the internal faces of the cross. static bool testCrossCollapse(PosType &p, std::vector ff, std::vector vi, Point3 &mp, Params ¶ms) { if(!checkFacesAfterCollapse(ff, p, mp, params, true)) return false; return true; } /* *Choose the best way to collapse a cross based on the (external) cross vertices valence *and resulting face quality * +0 -1 * v1 v1 v1 * /| \ /|\ / \ * / | \ / | \ / \ * / | \ / | \ / \ * / *p| \ -1/ | \ -1 +0/ \+0 * v0-------- v2 ========> v0 | v2 OR v0-------v2 * \ | / \ | / \ / * \ | / \ | / \ / * \ | / \ | / \ / * \ | / \|/ +0 \ / -1 * v3 v3 v3 */ static bool chooseBestCrossCollapse(PosType &p, VertexPair& bp, std::vector &ff) { std::vector vv0, vv1, vv2, vv3; VertexType *v0, *v1, *v2, *v3; v0 = p.F()->V1(p.VInd()); v1 = p.F()->V2(p.VInd()); bool crease[4] = {false, false, false, false}; crease[0] = p.F()->IsFaceEdgeS(VtoE(p.VInd(), (p.VInd()+1)%3)); crease[1] = p.F()->IsFaceEdgeS(VtoE(p.VInd(), (p.VInd()+2)%3)); for(FaceType *f: ff) if(!(*f).IsD() && f != p.F()) { PosType pi(f, p.V()); VertexType *fv1 = pi.F()->V1(pi.VInd()); VertexType *fv2 = pi.F()->V2(pi.VInd()); if(fv1 == v0 || fv2 == v0) { if (fv1 == 0) { v3 = fv2; crease[3] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+2)%3)); } else { v3 = fv1; crease[3] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+1)%3)); } // v3 = (fv1 == v0) ? fv2 : fv1; } if(fv1 == v1 || fv2 == v1) { if (fv1 == v1) { v2 = fv2; crease[2] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+2)%3)); } else { v2 = fv1; crease[2] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+1)%3)); } // v2 = (fv1 == v1) ? fv2 : fv1; } } } //Cross Collapse pass: This pass cleans the mesh from cross vertices, keeping in mind the link conditions //and feature preservations tests. static void CollapseCrosses(MeshType &m , Params ¶ms) { tri::UpdateTopology::VertexFace(m); tri::UpdateFlags::VertexBorderFromNone(m); int count = 0; SelectionStack ss(m); ss.push(); { tri::UpdateTopology::FaceFace(m); Clean::CountNonManifoldVertexFF(m,true); //From now on Selection on vertices is not manifoldness ForEachFace(m, [&](FaceType &f) { if(!f.IsD() && (params.selectedOnly == false || f.IsS())) { for(auto i=0; i<3; ++i) { PosType pi(&f, i); if(!pi.V()->IsB()) { std::vector ff; std::vector vi; face::VFStarVF(pi.V(), ff, vi); //if cross need to check what creases you have and decide where to collapse accordingly //if tricuspidis need whenever you have at least one crease => can't collapse anywhere if(ff.size() == 4 || ff.size() == 3) { // VertexPair bp; VertexPair bp = VertexPair(pi.V(), pi.VFlip()); Point3 mp = (pi.V()->P()+pi.VFlip()->P())/2.f; if(testCollapse1(pi, bp, mp, 0, 0, params, true) && Collapser::LinkConditions(bp)) { Collapser::Do(m, bp, mp, true); ++params.stat.collapseNum; ++count; break; } } } } } }); } ss.pop(); Allocator::CompactEveryVector(m); } // This function sets the selection bit on vertices that lie on creases static int selectVertexFromCrease(MeshType &m, ScalarType creaseThr) { int count = 0; Clean::CountNonManifoldVertexFF(m, true, false); ForEachFacePos(m, [&](PosType &p){ if(p.IsBorder() || p.IsEdgeS()/*testCreaseEdge(p, creaseThr)*/) { p.V()->SetS(); p.VFlip()->SetS(); ++count; } }); return count; } static int selectVertexFromFold(MeshType &m, Params & params) { std::vector creaseVerts(m.VN(), 0); ForEachFacePos(m, [&] (PosType & p) { if (p.IsEdgeS()) { creaseVerts[vcg::tri::Index(m, p.V())] = 1; creaseVerts[vcg::tri::Index(m, p.VFlip())] = 1; } }); ForEachFace(m, [&] (FaceType & f) { for (int i = 0; i < 3; ++i) { if (f.FFp(i) > &f) { ScalarType angle = fastAngle(NormalizedTriangleNormal(f), NormalizedTriangleNormal(*(f.FFp(i)))); if (angle <= params.foldAngleCosThr) { if (creaseVerts[vcg::tri::Index(m, f.V0(i))] == 0) f.V0(i)->SetS(); if (creaseVerts[vcg::tri::Index(m, f.V1(i))] == 0) f.V1(i)->SetS(); if (creaseVerts[vcg::tri::Index(m, f.V2(i))] == 0) f.V2(i)->SetS(); if (creaseVerts[vcg::tri::Index(m, f.FFp(i)->V2(f.FFi(i)))] == 0) f.FFp(i)->V2(f.FFi(i))->SetS(); } } } }); return 0; } static void FoldRelax(MeshType &m, Params params, const int step, const bool strict = true) { typename vcg::tri::Smooth::LaplacianInfo lpz(CoordType(0, 0, 0), 0); SimpleTempData::LaplacianInfo> TD(m.vert, lpz); const ScalarType maxDist = (strict) ? params.maxSurfDist / 1000. : params.maxSurfDist; for (int i = 0; i < step; ++i) { TD.Init(lpz); vcg::tri::Smooth::AccumulateLaplacianInfo(m, TD, false); for (auto fi = m.face.begin(); fi != m.face.end(); ++fi) { std::vector newPos(4); bool moving = false; for (int j = 0; j < 3; ++j) { newPos[j] = fi->cP(j); if (!fi->V(j)->IsD() && TD[fi->V(j)].cnt > 0) { if (fi->V(j)->IsS()) { newPos[j] = (fi->V(j)->P() + TD[fi->V(j)].sum) / (TD[fi->V(j)].cnt + 1); moving = true; } } } if (moving) { // const CoordType oldN = vcg::NormalizedTriangleNormal(*fi); // const CoordType newN = vcg::Normal(newPos[0], newPos[1], newPos[2]).Normalize(); newPos[3] = (newPos[0] + newPos[1] + newPos[2]) / 3.; if (/*(strict || oldN * newN > 0.99) &&*/ (!params.surfDistCheck || testHausdorff(*params.mProject, params.grid, newPos, maxDist))) { for (int j = 0; j < 3; ++j) fi->V(j)->P() = newPos[j]; } } } } } static void VertexCoordPlanarLaplacian(MeshType &m, Params & params, int step, ScalarType delta = 0.2) { typename vcg::tri::Smooth::LaplacianInfo lpz(CoordType(0, 0, 0), 0); SimpleTempData::LaplacianInfo> TD(m.vert, lpz); for (int i = 0; i < step; ++i) { TD.Init(lpz); vcg::tri::Smooth::AccumulateLaplacianInfo(m, TD, false); // First normalize the AccumulateLaplacianInfo for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi) if (!(*vi).IsD() && TD[*vi].cnt > 0) { if ((*vi).IsS()) TD[*vi].sum = ((*vi).P() + TD[*vi].sum) / (TD[*vi].cnt + 1); } // for (auto fi = m.face.begin(); fi != m.face.end(); ++fi) // { // if (!(*fi).IsD()) // { // for (int j = 0; j < 3; ++j) // { // if (Angle(Normal(TD[(*fi).V0(j)].sum, (*fi).P1(j), (*fi).P2(j)), // Normal((*fi).P0(j), (*fi).P1(j), (*fi).P2(j))) > M_PI/2.) // TD[(*fi).V0(j)].sum = (*fi).P0(j); // } // } // } // for (auto fi = m.face.begin(); fi != m.face.end(); ++fi) // { // if (!(*fi).IsD()) // { // for (int j = 0; j < 3; ++j) // { // if (Angle(Normal(TD[(*fi).V0(j)].sum, TD[(*fi).V1(j)].sum, (*fi).P2(j)), // Normal((*fi).P0(j), (*fi).P1(j), (*fi).P2(j))) > M_PI/2.) // { // TD[(*fi).V0(j)].sum = (*fi).P0(j); // TD[(*fi).V1(j)].sum = (*fi).P1(j); // } // } // } // } for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi) if (!(*vi).IsD() && TD[*vi].cnt > 0) { std::vector newPos(1, TD[*vi].sum); if ((*vi).IsS() && testHausdorff(*params.mProject, params.grid, newPos, params.maxSurfDist)) (*vi).P() = (*vi).P() * (1-delta) + TD[*vi].sum * (delta); } } // end step } // static int /** * Simple Laplacian Smoothing step * Border and crease vertices are kept fixed. * If there are selected faces and the param.onlySelected is true we compute * the set of internal vertices to the selection and we combine it in and with * the vertexes not on border or creases */ static void ImproveByLaplacian(MeshType &m, Params params) { SelectionStack ss(m); if(params.selectedOnly) { ss.push(); tri::UpdateSelection::VertexFromFaceStrict(m); ss.push(); } tri::UpdateTopology::FaceFace(m); tri::UpdateFlags::VertexBorderFromFaceAdj(m); tri::UpdateSelection::VertexFromBorderFlag(m); selectVertexFromCrease(m, params.creaseAngleCosThr); tri::UpdateSelection::VertexInvert(m); if(params.selectedOnly) { ss.popAnd(); } VertexCoordPlanarLaplacian(m, params, 1); tri::UpdateSelection::VertexClear(m); selectVertexFromFold(m, params); FoldRelax(m, params, 2); tri::UpdateSelection::VertexClear(m); if(params.selectedOnly) { ss.pop(); } } /* Reprojection step, this method reprojects each vertex on the original surface sampling the nearest Point3 onto it using a uniform grid StaticGrid t */ //TODO: improve crease reprojection: // crease verts should reproject only on creases. static void ProjectToSurface(MeshType &m, Params & params) { for(auto vi=m.vert.begin();vi!=m.vert.end();++vi) if(!(*vi).IsD()) { Point3 newP, normP, barP; ScalarType maxDist = params.maxSurfDist * 2.5f, minDist = 0.f; FaceType* fp = GetClosestFaceBase(*params.mProject, params.grid, vi->cP(), maxDist, minDist, newP/*, normP, barP*/); if (fp != NULL) { vi->P() = newP; } } } }; } // end namespace tri } // end namespace vcg #endif