From 795f5473d611171ca37a1f17d682de4594748960 Mon Sep 17 00:00:00 2001 From: "T.Alderighi" Date: Wed, 11 Dec 2019 16:33:07 +0100 Subject: [PATCH] complete rework of the crease preserving strategy for isotropic_remeshing & removed manifoldness constraint. Small patch in edge_collapse adding a parameter to do (default=false) for crease information preservation after collapse Added refineMidpoint in refine to perform midpoint refinement without manifoldness constraints --- vcg/complex/algorithms/edge_collapse.h | 43 +- vcg/complex/algorithms/isotropic_remeshing.h | 811 ++++++++++++++----- vcg/complex/algorithms/refine.h | 228 ++++++ 3 files changed, 884 insertions(+), 198 deletions(-) diff --git a/vcg/complex/algorithms/edge_collapse.h b/vcg/complex/algorithms/edge_collapse.h index 0c329436..57da15b9 100644 --- a/vcg/complex/algorithms/edge_collapse.h +++ b/vcg/complex/algorithms/edge_collapse.h @@ -209,16 +209,38 @@ public: // Main Collapsing Function: the one that actually performs the collapse of the edge denoted by the VertexPair c // Remember that v[0] will be deleted and v[1] will survive with the position indicated by p // To do a collapse onto a vertex simply pass p as the position of the surviving vertex - static int Do(TriMeshType &m, VertexPair & c, const Point3 &p) + static int Do(TriMeshType &m, VertexPair & c, const Point3 &p, const bool preserveFaceEdgeS = false) { EdgeSet es; FindSets(c,es); int n_face_del=0 ; + + static int VtoE[3][3] = { -1, 0, 2, + 0, -1, 1, + 2, 1, -1 }; + +// bool toSel = false; + + VertexType* top[2]; + std::map toSel; + + std::vector v2s; v2s.reserve(2); + for(auto i=es.AV01().begin();i!=es.AV01().end();++i) { FaceType & f = *((*i).f); assert(f.V((*i).z) == c.V(0)); + + if (preserveFaceEdgeS && f.IsFaceEdgeS(VtoE[((*i).z+1)%3][((*i).z+2)%3])) + { +// std::cout << "2 " <::DeleteFace(m,f); @@ -231,11 +253,30 @@ public: // - we prepend that face to the list of the faces incident on v[1] for(auto i=es.AV0().begin();i!=es.AV0().end();++i) { + FaceType & f = *((*i).f); + + if (preserveFaceEdgeS) + { + for (size_t j = 0; j < v2s.size(); ++j) + { + if ((*i).f->V(((*i).z+1)%3) == v2s[j]) + { + (*i).f->SetFaceEdgeS(VtoE[((*i).z)%3][((*i).z+1)%3]); + break; + } + if ((*i).f->V(((*i).z+2)%3) == v2s[j]) + { + (*i).f->SetFaceEdgeS(VtoE[((*i).z)%3][((*i).z+2)%3]); + break; + } + } + } (*i).f->V((*i).z) = c.V(1); // For each face in v0 we substitute v0 with v1 (*i).f->VFp((*i).z) = c.V(1)->VFp(); (*i).f->VFi((*i).z) = c.V(1)->VFi(); c.V(1)->VFp() = (*i).f; c.V(1)->VFi() = (*i).z; + } Allocator::DeleteVertex(m,*(c.V(0))); diff --git a/vcg/complex/algorithms/isotropic_remeshing.h b/vcg/complex/algorithms/isotropic_remeshing.h index fc246dd6..a79147a9 100644 --- a/vcg/complex/algorithms/isotropic_remeshing.h +++ b/vcg/complex/algorithms/isotropic_remeshing.h @@ -32,6 +32,8 @@ #include #include +#include + namespace vcg { namespace tri { template @@ -41,7 +43,9 @@ public: typedef TRI_MESH_TYPE MeshType; typedef typename MeshType::FaceType FaceType; 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; @@ -66,6 +70,9 @@ public: ScalarType maxLength; // maximal admitted length: no edge should be longer than this value (used when refining) ScalarType lengthThr; + 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 @@ -78,17 +85,19 @@ public: bool smoothFlag=true; bool projectFlag=true; bool selectedOnly = false; + + bool userSelectedCreases = false; + bool surfDistCheck = true; + bool adapt=false; int iter=1; Stat stat; void SetTargetLen(const ScalarType len) { - // minLength=len; - // maxLength=len*2.0; - // lengthThr=len*2.0; minLength=len*4./5.; maxLength=len*4./3.; lengthThr=len*4./3.; + minimalAdmittedArea = (minLength * minLength)/1000.0; } void SetFeatureAngleDeg(const ScalarType angle) { @@ -96,6 +105,9 @@ public: creaseAngleCosThr = cos(creaseAngleRadThr); } + StaticGrid grid; + MeshType* m; + MeshType* mProject; } Params; @@ -111,28 +123,28 @@ public: } static void Do(MeshType &toRemesh, MeshType &toProject, Params & params, vcg::CallBackPos * cb=0) { - // std::cout << "Enter" << std::endl; assert(&toRemesh != &toProject); params.stat.Reset(); - StaticGrid t; - tri::FaceTmark mark; + tri::UpdateBounding::Box(toRemesh); - //avoid useless grid initializations... - if (params.projectFlag) { - t.Set(toProject.face.begin(), toProject.face.end()); - mark.SetMesh(&toProject); + tri::UpdateBounding::Box(toProject); + tri::UpdateNormal::PerFaceNormalized(toProject); + params.m = &toRemesh; + params.mProject = &toProject; + params.grid.Set(toProject.face.begin(), toProject.face.end()); } tri::UpdateTopology::FaceFace(toRemesh); tri::UpdateFlags::VertexBorderFromFaceAdj(toRemesh); - - // tri::MeshAssert::FFTwoManifoldEdge(toRemesh); tri::UpdateTopology::VertexFace(toRemesh); -// computeQuality(toRemesh); + + // computeQuality(toRemesh); // tri::UpdateQuality::VertexSaturate(toRemesh); + tagCreaseEdges(toRemesh, params); + for(int i=0; i < params.iter; ++i) { // params.stat.Reset(); @@ -141,11 +153,35 @@ public: if(params.splitFlag) SplitLongEdges(toRemesh, params); +#ifdef DEBUG_CREASE + 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; + }); + std::string name = "creases" + std::to_string(i) + ".ply"; + vcg::tri::io::Exporter::Save(toRemesh, name.c_str(), vcg::tri::io::Mask::IOM_ALL); +#endif if(params.collapseFlag) { CollapseShortEdges(toRemesh, params); CollapseCrosses(toRemesh, params); + } if(params.swapFlag) @@ -153,14 +189,22 @@ public: if(params.smoothFlag) ImproveByLaplacian(toRemesh, params); - if(params.projectFlag) - ProjectToSurface(toRemesh, t, mark); + if(params.projectFlag) + ProjectToSurface(toRemesh, params); } - // std::cout << "exit" << std::endl; } 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(Point3 n0, Point3 n1) @@ -239,6 +283,143 @@ private: return (int)(std::ceil(angleSumRad / (M_PI/3.0f))); } + + static bool testHausdorff (MeshType & m, StaticGrid & grid, const std::vector & verts, const ScalarType maxD) + { + for (CoordType v : verts) + { + CoordType closest; + ScalarType dist = 0; + FaceType* fp = GetClosestFaceBase(m, grid, v, maxD, dist, closest); + + if (fp == NULL) + { + return false; + } + } + return true; + } + + static int tagCreaseEdges(MeshType &m, Params & params) + { + int count = 0; + std::vector creaseVerts(m.VN(), 0); + + vcg::tri::UpdateFlags::VertexClearV(m); + std::queue creaseQueue; + ForEachFacePos(m, [&](PosType &p){ + if((p.FFlip() > p.F()) || p.IsBorder()) + { + if (!params.userSelectedCreases && (testCreaseEdge(p, params.creaseAngleCosThr) || p.IsBorder())) + { + p.F()->SetFaceEdgeS(p.E()); + p.FlipF(); + p.F()->SetFaceEdgeS(p.E()); + creaseQueue.push(p); + } + } + }); + + // //now all creases are checked... + // //prune false positive (too small) (count + scale?) + + // while (!creaseQueue.empty()) + // { + // PosType & p = creaseQueue.front(); + // creaseQueue.pop(); + + // std::stack chainQueue; + // std::vector chainVerts; + + // if (!p.V()->IsV()) + // { + // chainQueue.push(p); + // } + + // p.FlipV(); + // p.NextEdgeS(); + + // if (!p.V()->IsV()) + // { + // chainQueue.push(p); + // } + + // while (!chainQueue.empty()) + // { + // PosType p = chainQueue.top(); + // chainQueue.pop(); + + // p.V()->SetV(); + // chainVerts.push_back(vcg::tri::Index(m, p.V())); + + // PosType pp = p; + + // //circle around vert in search for new crease edges + // do { + // pp.NextF(); //jump adj face + // pp.FlipE(); // this edge is already ok => jump to next + // if (pp.IsEdgeS()) + // { + // PosType nextPos = pp; + // nextPos.FlipV(); // go to next vert in the chain + // if (!nextPos.V()->IsV()) // if already visited...ignore + // { + // chainQueue.push(nextPos); + // } + // } + // } + // while (pp != p); + + // } + + // if (chainVerts.size() > 5) + // { + // for (auto vp : chainVerts) + // { + // creaseVerts[vp] = 1; + // } + // } + // } + // //store crease on V() + + // //this aspect ratio check doesn't work on cadish meshes (long thin triangles spanning whole mesh) + // ForEachFace(m, [&] (FaceType & f) { + // if (vcg::QualityRadii(f.cP(0), f.cP(1), f.cP(2)) < params.aspectRatioThr) + // { + // if (creaseVerts[vcg::tri::Index(m, f.V(0))] == 0) + // f.V(0)->SetS(); + // if (creaseVerts[vcg::tri::Index(m, f.V(1))] == 0) + // f.V(1)->SetS(); + // if (creaseVerts[vcg::tri::Index(m, f.V(2))] == 0) + // f.V(2)->SetS(); + // } + // }); + + // 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 count; + } + + /* Edge Swap Step: This method optimizes the valence of each vertex. @@ -247,11 +428,11 @@ private: 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 + v1 v1 + / \ /|\ + / \ / | \ + / \ / | \ + / _*p\ -1/ | \ -1 v2--------v0 ========> v2 | v0 \ / \ | / \ / \ | / @@ -263,29 +444,36 @@ private: static bool testSwap(PosType p, ScalarType creaseAngleCosThr) { //if border or feature, do not swap - if(p.IsBorder() || testCreaseEdge(p, creaseAngleCosThr)) return false; + if (/*p.IsBorder() || */p.IsEdgeS()) return false; int oldDist = 0, newDist = 0, idealV, actualV; PosType tp=p; VertexType *v0=tp.V(); - idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices(); + + std::vector incident; + + vcg::face::VVStarVF(tp.V(), incident); + idealV = idealValence(tp); actualV = incident.size(); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV - 1)); - tp.FlipF();tp.FlipE();tp.FlipV(); + tp.NextF();tp.FlipE();tp.FlipV(); VertexType *v1=tp.V(); - idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices(); + vcg::face::VVStarVF(tp.V(), incident); + idealV = idealValence(tp); actualV = incident.size(); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV + 1)); tp.FlipE();tp.FlipV();tp.FlipE(); VertexType *v2=tp.V(); - idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices(); + vcg::face::VVStarVF(tp.V(), incident); + idealV = idealValence(tp); actualV = incident.size(); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV - 1)); - tp.FlipF();tp.FlipE();tp.FlipV(); + tp.NextF();tp.FlipE();tp.FlipV(); VertexType *v3=tp.V(); - idealV = idealValence(tp); actualV = tp.NumberOfIncidentVertices(); + vcg::face::VVStarVF(tp.V(), incident); + idealV = idealValence(tp); actualV = incident.size(); oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV + 1)); ScalarType qOld = std::min(Quality(v0->P(),v2->P(),v3->P()),Quality(v0->P(),v1->P(),v2->P())); @@ -295,22 +483,65 @@ private: (newDist == oldDist && qNew > qOld * 1.f) || qNew > 1.5f * qOld; } + static bool checkManifoldness(FaceType & f, int z) + { + PosType pos(&f, (z+2)%3, f.V2(z)); + 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) { static ScalarType foldCheckRad = math::ToRad(5.); tri::UpdateTopology::FaceFace(m); - ForEachFacePos(m, [&](PosType &p){ - if(p.FFlip() > p.F()) - if(((!params.selectedOnly) || (p.F()->IsS() && p.FFlip()->IsS())) && - face::CheckFlipEdge(*p.F(), p.E()) && - testSwap(p, params.creaseAngleCosThr) && - face::CheckFlipEdgeNormal(*p.F(), p.E(), vcg::math::ToRad(5.))) // ? broken? //TODO: fix //BUG:fix - // face::CheckFlipEdgeNormal(*p.F(), p.E(), params.creaseAngleRadThr * 0.85)) // ? broken? //TODO: fix //BUG:fix + 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) { - face::FlipEdge(*p.F(), p.E()); - ++params.stat.flipNum; + if (&f > f.cFFp(i)) + { + PosType pi(&f, i); + CoordType swapEdgeMidPoint = (f.cP2(i) + f.cFFp(i)->cP2(f.cFFi(i))) / 2.; + std::vector toCheck(1, swapEdgeMidPoint); + + if(((!params.selectedOnly) || (f.IsS() && f.cFFp(i)->IsS())) && + face::IsManifold(f, i) && checkManifoldness(f, i) && + face::CheckFlipEdge(f, i) && + testSwap(pi, params.creaseAngleCosThr) && + (!params.surfDistCheck || testHausdorff(*params.mProject, params.grid, toCheck, params.maxSurfDist)) && + face::CheckFlipEdgeNormal(f, i, vcg::math::ToRad(5.))) + { + //When doing the swap we need to preserve and update the crease info accordingly + FaceType* g = f.cFFp(i); + int w = f.FFi(i); + + bool creaseF = g->IsFaceEdgeS((w + 1) % 3); + bool creaseG = f.IsFaceEdgeS((i + 1) % 3); + + face::FlipEdge(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; + } + } } }); } @@ -373,33 +604,54 @@ private: else { EdgeSplitLenPred ep; ep.squaredlengthThr = params.maxLength*params.maxLength; - tri::RefineE(m,midFunctor,ep,params.selectedOnly); + tri::RefineMidpoint(m, ep, params.selectedOnly); params.stat.splitNum+=ep.count; } } - //Geometric check on feasibility of the collapse of the given pos - //The check fails if: - // -new face has too bad quality. - // -new face normal changes too much after collapse. - // -new face has too long edges. - // TRY: if the vertex has valence 4 (cross vertex) we relax the check on length - //TODO: Refine the crease preservance check when collapsing along boundary (or in general maybe) WORK on this - static bool checkCollapseFacesAroundVert(PosType &p, Point3 &mp, Params & params, bool relaxed=false, bool crease=false) + static int VtoE(const int v0, const int v1) { - ScalarType minimalAdmittedArea = (params.minLength * params.minLength)/10000.0; + static /*constexpr*/ int Vmat[3][3] = { -1, 0, 2, + 0, -1, 1, + 2, 1, -1}; + return Vmat[v0][v1]; + } - vector ff; - vector vi; - face::VFStarVF(p.V(), ff, vi); + static bool checkCanMoveOnCollapse(PosType p, std::vector & faces, std::vector & vIdxes, Params ¶ms) + { bool allIncidentFaceSelected = true; - for(FaceType *f: ff) + PosType pi = p; + + CoordType dEdgeVector = (p.V()->cP() - p.VFlip()->cP()).Normalize(); + + for (size_t i = 0; i < faces.size(); ++i) + { + if (faces[i]->IsFaceEdgeS(VtoE(vIdxes[i], (vIdxes[i]+1)%3))) + { + CoordType movingEdgeVector0 = (faces[i]->cP1(vIdxes[i]) - faces[i]->cP(vIdxes[i])).Normalize(); + if (std::fabs(movingEdgeVector0 * dEdgeVector) < 1.f) + return false; + } + if (faces[i]->IsFaceEdgeS(VtoE(vIdxes[i], (vIdxes[i]+2)%3))) + { + CoordType movingEdgeVector1 = (faces[i]->cP2(vIdxes[i]) - faces[i]->cP(vIdxes[i])).Normalize(); + if (std::fabs(movingEdgeVector1 * dEdgeVector) < 1.f) + return false; + } + allIncidentFaceSelected &= faces[i]->IsS(); + } + + return params.selectedOnly ? allIncidentFaceSelected : true; + } + + static bool checkFacesAfterCollapse (std::vector & faces, 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 { - allIncidentFaceSelected &= f->IsS(); - PosType pi(f, p.V()); //same vertex VertexType *v0 = pi.V(); @@ -409,60 +661,172 @@ private: if( v1 == p.VFlip() || v2 == p.VFlip()) //i'm the other deleted face continue; - //TODO: Fixme this is bug + //check on new face quality { - ScalarType div = fastAngle(NormalizedTriangleNormal(*(p.F())), NormalizedTriangleNormal(*f)); - if(div <= params.creaseAngleCosThr && div >= -0.93) - return false; - } + ScalarType newQ = Quality(mp, v1->P(), v2->P()); + ScalarType oldQ = Quality(v0->P(), v1->P(), v2->P()); - float area = DoubleArea(*(pi.F()))/2.f; - - //quality and normal divergence checks - ScalarType newQ = Quality(mp, v1->P(), v2->P()); - ScalarType oldQ = Quality(v0->P(), v1->P(), v2->P()); - - if(area > minimalAdmittedArea) // for triangles not too small - { if( newQ <= 0.5*oldQ ) return false; + } - Point3 oldN = NormalizedTriangleNormal(*(pi.F())); - Point3 newN = Normal(mp, v1->P(), v2->P()).Normalize(); + // we prevent collapse that makes edges too long (except for cross) + if(!relaxed) + if((Distance(mp, v1->P()) > params.maxLength || Distance(mp, v2->P()) > params.maxLength)) + return false; - float div = fastAngle(oldN, newN); - if(crease && div < 0.98) return false; + Point3 oldN = NormalizedTriangleNormal(*(pi.F())); + Point3 newN = Normal(mp, v1->P(), v2->P()).Normalize(); - // we prevent collapse that makes edges too long (except for cross) - if(!relaxed) - if((Distance(mp, v1->P()) > params.maxLength || Distance(mp, v2->P()) > params.maxLength)) - return false; + float div = fastAngle(oldN, newN); + if(div < 0.0 ) return false; + +// // check on new face distance from original mesh + if (params.surfDistCheck) + { + std::vector points(4); + points[0] = (v1->cP() + v2->cP() + mp) / 3.; + points[1] = (v1->cP() + mp) / 2.; + points[2] = (v2->cP() + mp) / 2.; + points[3] = mp; + if (!testHausdorff(*(params.mProject), params.grid, points, params.maxSurfDist)) + return false; } } - - if(params.selectedOnly) return allIncidentFaceSelected; + } return true; } - // Collapse test: Usual collapse test (check on target length) plus borders and crease handling - // and adaptivity. - static bool testCollapse(PosType &p, Point3 &mp, ScalarType minQ, ScalarType maxQ, Params ¶ms, bool relaxed = false) + + //TODO: Refactor code and implement the correct set up of crease info when collapsing towards a crease edge + static bool checkCollapseFacesAroundVert1(PosType &p, Point3 &mp, Params ¶ms, bool relaxed) + { + PosType p0 = p, p1 = p; + + p1.FlipV(); + + vector vi0, vi1; + vector ff0, ff1; + + face::VFStarVF(p0.V(), ff0, vi0); + face::VFStarVF(p1.V(), ff1, vi1); + + //check crease-moveability + bool moveable0 = checkCanMoveOnCollapse(p0, ff0, vi0, params); + bool moveable1 = checkCanMoveOnCollapse(p1, ff1, vi1, params); + + //if both moveable => go to midpoint + // else collapse on movable one + if (!moveable0 && !moveable1) + return false; + + //casting int(true) is always 1 and int(false) = =0 + assert(int(true) == 1); + assert(int(false) == 0); + mp = (p0.V()->cP() * int(moveable1) + p1.V()->cP() * int(moveable0)) / (int(moveable0) + int(moveable1)); + + if (!moveable0) + p = p0; + else + p = p1; + + if (checkFacesAfterCollapse(ff0, p0, mp, params, relaxed)) + return checkFacesAfterCollapse(ff1, p1, mp, params, relaxed); + + return false; + } + +// //Geometric check on feasibility of the collapse of the given pos +// //The check fails if: +// // -new face has too bad quality. +// // -new face normal changes too much after collapse. +// // -new face has too long edges. +// // TRY: if the vertex has valence 4 (cross vertex) we relax the check on length +// //TODO: Refine the crease preservance check when collapsing along boundary (or in general maybe) WORK on this +// static bool checkCollapseFacesAroundVert(PosType &p, Point3 &mp, Params & params, bool relaxed=false, bool crease=false) +// { +// ScalarType minimalAdmittedArea = (params.minLength * params.minLength)/10000.0; + +// vector ff; +// vector vi; +// face::VFStarVF(p.V(), ff, vi); + +// bool allIncidentFaceSelected = true; + +// for(FaceType *f: ff) +// if(!(*f).IsD() && f != p.F()) //i'm not a deleted face +// { +// allIncidentFaceSelected &= f->IsS(); + +// PosType pi(f, p.V()); //same vertex + +// VertexType *v0 = pi.V(); +// VertexType *v1 = pi.F()->V1(pi.VInd()); +// VertexType *v2 = pi.F()->V2(pi.VInd()); + +// if( v1 == p.VFlip() || v2 == p.VFlip()) //i'm the other deleted face +// continue; + +// //check on new face area +// { +// float area = DoubleArea(*(pi.F()))/2.f; + +// if (area < params.minimalAdmittedArea) +// return false; +// } + + + +// float area = DoubleArea(*(pi.F()))/2.f; + +// //quality and normal divergence checks +// ScalarType newQ = Quality(mp, v1->P(), v2->P()); +// ScalarType oldQ = Quality(v0->P(), v1->P(), v2->P()); + +// if(area > minimalAdmittedArea) // for triangles not too small +// { +// if( newQ <= 0.5*oldQ ) +// return false; + +// // we prevent collapse that makes edges too long (except for cross) +// if(!relaxed) +// if((Distance(mp, v1->P()) > params.maxLength || Distance(mp, v2->P()) > params.maxLength)) +// return false; + +// Point3 oldN = NormalizedTriangleNormal(*(pi.F())); +// Point3 newN = Normal(mp, v1->P(), v2->P()).Normalize(); +//// float div = fastAngle(oldN, newN); +//// if(crease && div < 0.98) return false; +//// { +//// std::vector points(3); +//// points[0] = (v1->cP() + v2->cP() + mp) / 3.; +//// points[1] = (v1->cP() + v0->cP()) / 2.; +//// points[2] = (v2->cP() + v0->cP()) / 2.; + +//// if (!testHausdorff(*(params.mProject), params.grid, points, params.maxSurfDist)) +//// return false; + +//// } +// } +// } + +// if(params.selectedOnly) return allIncidentFaceSelected; +// return true; +// } + + static bool testCollapse1(PosType &p, Point3 &mp, ScalarType minQ, ScalarType maxQ, Params ¶ms, bool relaxed = false) { ScalarType mult = (params.adapt) ? math::ClampedLerp((ScalarType)0.5,(ScalarType)1.5, (((math::Abs(p.V()->Q())+math::Abs(p.VFlip()->Q()))/(ScalarType)2.0)/(maxQ-minQ))) : (ScalarType)1; ScalarType dist = Distance(p.V()->P(), p.VFlip()->P()); ScalarType thr = mult*params.minLength; ScalarType area = DoubleArea(*(p.F()))/2.f; - if(dist < thr || area < params.minLength*params.minLength/100.f)//if to collapse + if(relaxed || (dist < thr || area < params.minLength*params.minLength/100.f))//if to collapse { - PosType pp = p; p.FlipV(); - //check all faces around p() and p.vflip() - //TODO: check things here...need to define good check for creases (spikes, corners, edges) - bool crease = testCreaseEdge(p, params.creaseAngleCosThr); - return checkCollapseFacesAroundVert(p, mp, params, relaxed, crease) && - checkCollapseFacesAroundVert(pp, mp, params, relaxed, crease); + return checkCollapseFacesAroundVert1(p, 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) @@ -508,6 +872,7 @@ private: tri::UpdateTopology::VertexFace(m); tri::UpdateFlags::FaceBorderFromVF(m); tri::UpdateFlags::VertexBorderFromFaceBorder(m); + tri::UpdateFlags::FaceClearS(m); for(auto fi=m.face.begin(); fi!=m.face.end(); ++fi) if(!(*fi).IsD() && (params.selectedOnly == false || fi->IsS())) @@ -519,25 +884,12 @@ private: VertexPair bp = VertexPair(pi.V(), pi.VFlip()); Point3 mp = (pi.V()->P()+pi.VFlip()->P())/2.f; - bool boundary = false; - //if both border or both internal use midpoint - if(pi.V()->IsB() == pi.VFlip()->IsB()) + if(testCollapse1(pi, mp, minQ, maxQ, params) && Collapser::LinkConditions(bp)) { - //if both border but not on border edge..abort..if chooseBoundaryCollapse can't find good collapse..abort.. - if(pi.V()->IsB() && !fi->IsB(pi.E()) && !(boundary = chooseBoundaryCollapse(pi, bp))) - continue; - mp = (pi.V()->IsB()) ? bp.V(1)->P() : (pi.V()->P()+pi.VFlip()->P())/2.f; + //collapsing on pi.V() + bp = VertexPair(pi.VFlip(), pi.V()); - } - else //if only one is border...collapse on it! - { -// continue; - bp = (pi.V()->IsB()) ? VertexPair(pi.VFlip(), pi.V()) : VertexPair(pi.V(), pi.VFlip()); - mp = (pi.VFlip()->IsB()) ? pi.V()->P() : pi.VFlip()->P(); - } - if(testCollapse(pi, mp, minQ, maxQ, params, boundary) && Collapser::LinkConditions(bp)) - { - Collapser::Do(m, bp, mp); + Collapser::Do(m, bp, mp, true); ++params.stat.collapseNum; break; } @@ -550,9 +902,9 @@ private: //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, Point3 &mp, Params ¶ms) + static bool testCrossCollapse(PosType &p, std::vector ff, std::vector vi, Point3 &mp, Params ¶ms) { - if(!checkCollapseFacesAroundVert(p, mp, params, true)) + if(!checkFacesAfterCollapse(ff, p, mp, params, true)) return false; return true; } @@ -571,7 +923,7 @@ private: // \ | / \ | / \ / // \ | / \|/ +0 \ / -1 // v3 v3 v3 - static VertexPair chooseBestCrossCollapse(PosType &p, vector &ff) + static bool chooseBestCrossCollapse(PosType &p, VertexPair& bp, vector &ff) { vector vv0, vv1, vv2, vv3; VertexType *v0, *v1, *v2, *v3; @@ -579,6 +931,12 @@ private: 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()) { @@ -587,9 +945,34 @@ private: VertexType *fv2 = pi.F()->V2(pi.VInd()); if(fv1 == v0 || fv2 == v0) - v3 = (fv1 == v0) ? fv2 : fv1; + { + 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) - v2 = (fv1 == v1) ? fv2 : fv1; + { + 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; + } } face::VVStarVF(v0, vv0); @@ -606,10 +989,36 @@ private: ScalarType Q1 = std::min(Quality(v0->P(), v1->P(), v3->P()), Quality(v1->P(), v2->P(), v3->P())); ScalarType Q2 = std::min(Quality(v0->P(), v1->P(), v2->P()), Quality(v2->P(), v3->P(), v0->P())); + if (crease[0] || crease[1] || crease[2] || crease[3]) + return false; +// if (crease[0] && crease[1] && crease[2] && crease[3]) +// { +// return false; +// } + +// if (crease[0] || crease[2]) +// { +// bp = VertexPair(p.V(), v0); +// return true; +// } + +// if (crease[1] || crease[3]) +// { +// bp = VertexPair(p.V(), v1); +// return true; +// } + + //no crease if(delta1 < delta2 && Q1 >= 0.6f*Q2) - return VertexPair(p.V(), v1); + { + bp = VertexPair(p.V(), v1); + return true; + } else - return VertexPair(p.V(), v0); + { + bp = VertexPair(p.V(), v0); + return true; + } } //Cross Collapse pass: This pass cleans the mesh from cross vertices, keeping in mind the link conditions //and feature preservations tests. @@ -632,15 +1041,47 @@ private: vector vi; face::VFStarVF(pi.V(), ff, vi); - //removing crosses and tricuspidis only + //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 = (ff.size() == 4) ? chooseBestCrossCollapse(pi, ff) : VertexPair(pi.V(), pi.VFlip()); - Point3 mp = bp.V(1)->P(); - //todo: think about if you should try doing the other collapse if test or link fails for this one - if(testCrossCollapse(pi, mp, params) && Collapser::LinkConditions(bp)) +// VertexPair bp; + VertexPair bp = VertexPair(pi.V(), pi.VFlip()); + Point3 mp = (pi.V()->P()+pi.VFlip()->P())/2.f; + +// if (ff.size() == 4) +// { +// //avoid collapsing if creases don't allow to +//// continue; +// if (!chooseBestCrossCollapse(pi, bp, ff)) +// continue; +// } +// else //tricuspidis +// { +// bool collapse = true; +// for (int i = 0; i < ff.size(); ++i) +// { +// PosType pp(ff[i], pi.V()); + +// if (pp.IsFaceS()) +// collapse = false; +// pp.FlipE(); +// if (pp.IsFaceS()) +// collapse = false; +// } +// if (!collapse) +// continue; +// else bp = VertexPair(pi.V(), pi.VFlip()); +// } + +//// VertexPair bp = (ff.size() == 4) ? chooseBestCrossCollapse(pi, ff) : VertexPair(pi.V(), pi.VFlip()); +// Point3 mp = bp.V(1)->P(); + +// //todo: think about if you should try doing the other collapse if test or link fails for this one +// if(testCrossCollapse(pi, ff, vi, mp, params) && Collapser::LinkConditions(bp)) + if(testCollapse1(pi, mp, 0, 0, params) && Collapser::LinkConditions(bp)) { - Collapser::Do(m, bp, mp); + Collapser::Do(m, bp, mp, true); ++count; break; } @@ -656,7 +1097,7 @@ private: { int count = 0; ForEachFacePos(m, [&](PosType &p){ - if((p.FFlip() > p.F()) && testCreaseEdge(p, creaseThr)) + if((p.FFlip() > p.F()) && p.IsEdgeS()/*testCreaseEdge(p, creaseThr)*/) { p.V()->SetS(); p.VFlip()->SetS(); @@ -668,82 +1109,15 @@ private: static int selectVertexFromFold(MeshType &m, Params & params) { - int count = 0; std::vector creaseVerts(m.VN(), 0); - - vcg::tri::UpdateFlags::VertexClearV(m); - std::queue creaseQueue; - ForEachFacePos(m, [&](PosType &p){ - if((p.FFlip() > p.F())) + ForEachFacePos(m, [&] (PosType & p) { + if (p.IsEdgeS()) { - ScalarType angle = (NormalizedTriangleNormal(*p.F()) * NormalizedTriangleNormal(*(p.FFlip()))); - if (angle <= params.creaseAngleCosThr && angle > -0.95) - { - p.F()->SetFaceEdgeS(p.E()); - p.FFlip()->SetFaceEdgeS(p.F()->FFi(p.E())); - creaseQueue.push(p); - } + creaseVerts[vcg::tri::Index(m, p.V())] = 1; + creaseVerts[vcg::tri::Index(m, p.VFlip())] = 1; } }); - while (!creaseQueue.empty()) - { - PosType & p = creaseQueue.front(); - creaseQueue.pop(); - - std::stack chainQueue; - std::vector chainVerts; - - if (!p.V()->IsV()) - { - chainQueue.push(p); - } - p.FlipV(); - - if (!p.V()->IsV()) - { - chainQueue.push(p); - } - - while (!chainQueue.empty()) - { - PosType p = chainQueue.top(); - chainQueue.pop(); - - p.V()->SetV(); - chainVerts.push_back(vcg::tri::Index(m, p.V())); - - PosType pp = p; - auto count = 1; - - //circle around vert in search for new crease edges - do { - pp.FlipF(); //jump adj face - pp.FlipE(); // this edge is already ok => jump to next - if (pp.IsEdgeS()) - { - PosType nextPos = pp; - nextPos.FlipV(); // go to next vert in the chain - if (!nextPos.V()->IsV()) // if already visited...ignore - { - ++count; - chainQueue.push(nextPos); - } - } - } - while (pp != p); - - } - - if (chainVerts.size() > 6) - { - for (auto vp : chainVerts) - { - creaseVerts[vp] = 1; - } - } - } - //store crease on V() //this aspect ratio check doesn't work on cadish meshes (long thin triangles spanning whole mesh) ForEachFace(m, [&] (FaceType & f) { @@ -758,6 +1132,7 @@ private: } }); + ForEachFace(m, [&] (FaceType & f) { for (int i = 0; i < 3; ++i) { @@ -779,9 +1154,48 @@ private: } }); - return count; + return 0; } + static void FoldRelax(MeshType &m, Params params, const int step) + { + 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, true); + + 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) + { + newPos[3] = (newPos[0] + newPos[1] + newPos[2]) / 3.; + if (!params.surfDistCheck || testHausdorff(*params.mProject, params.grid, newPos, params.maxSurfDist)) + { + for (int j = 0; j < 3; ++j) + fi->V(j)->P() = newPos[j]; + } + } + } + } + } // static int /** * Simple Laplacian Smoothing step @@ -810,9 +1224,9 @@ private: tri::Smooth::VertexCoordPlanarLaplacian(m, 1, math::ToRad(1.0), true); tri::UpdateSelection::VertexClear(m); - selectVertexFromFold(m, params); - tri::Smooth::VertexCoordLaplacian(m, 1, true, true); + selectVertexFromFold(m, params); + FoldRelax(m, params, 3); tri::UpdateSelection::VertexClear(m); @@ -824,16 +1238,19 @@ private: Reprojection step, this method reprojects each vertex on the original surface sampling the nearest Point3 onto it using a uniform grid StaticGrid t */ - static void ProjectToSurface(MeshType &m, StaticGrid t, FaceTmark mark) + static void ProjectToSurface(MeshType &m, Params & params) { - face::PointDistanceBaseFunctor distFunct; - ScalarType maxDist = std::numeric_limits::max(), minDist = 0.f; for(auto vi=m.vert.begin();vi!=m.vert.end();++vi) if(!(*vi).IsD()) { - Point3 newP; - t.GetClosest(distFunct, mark, vi->P(), maxDist, minDist, newP); - vi->P() = newP; + Point3 newP, normP, barP; + ScalarType maxDist = params.maxSurfDist * 1.5f, minDist = 0.f; + FaceType* fp = GetClosestFaceBase(*params.mProject, params.grid, vi->cP(), maxDist, minDist, newP, normP, barP); + + if (fp != NULL) + { + vi->P() = newP; + } } } }; diff --git a/vcg/complex/algorithms/refine.h b/vcg/complex/algorithms/refine.h index 45fec2af..65212987 100644 --- a/vcg/complex/algorithms/refine.h +++ b/vcg/complex/algorithms/refine.h @@ -1049,6 +1049,234 @@ void TrivialMidPointRefine(MeshType & m) Allocator::CompactEveryVector(m); } + +template +bool RefineMidpoint(MESH_TYPE &m, EDGEPRED &ep, bool RefineSelected=false, CallBackPos *cb = 0) +{ + // common typenames + typedef typename MESH_TYPE::VertexIterator VertexIterator; + typedef typename MESH_TYPE::FaceIterator FaceIterator; + typedef typename MESH_TYPE::VertexPointer VertexPointer; + typedef typename MESH_TYPE::FacePointer FacePointer; + typedef typename MESH_TYPE::FaceType FaceType; + typedef typename MESH_TYPE::FaceType::TexCoordType TexCoordType; + + assert(tri::HasFFAdjacency(m)); + tri::UpdateFlags::FaceBorderFromFF(m); + typedef face::Pos PosType; + + int j,NewVertNum=0,NewFaceNum=0; + + typedef RefinedFaceData RFD; + typedef typename MESH_TYPE :: template PerFaceAttributeHandle HandleType; + HandleType RD = tri::Allocator:: template AddPerFaceAttribute (m,std::string("RefineData")); + + MidPoint mid(&m); + // Callback stuff + int step=0; + int PercStep=std::max(1,m.fn/33); + + // First Loop: We analyze the mesh to compute the number of the new faces and new vertices + FaceIterator fi; + for(fi=m.face.begin(),j=0;fi!=m.face.end();++fi) if(!(*fi).IsD()) + { + if(cb && (++step%PercStep)==0) (*cb)(step/PercStep,"Refining..."); + // skip unselected faces if necessary + if(RefineSelected && !(*fi).IsS()) continue; + + for(j=0;j<3;j++) + { + if(RD[fi].ep[j]) continue; + + PosType edgeCur(&*fi,j); + if(RefineSelected && ! edgeCur.FFlip()->IsS()) continue; + if(!ep(edgeCur)) continue; + + RD[edgeCur.F()].ep[edgeCur.E()]=true; + ++NewFaceNum; + ++NewVertNum; + PosType start = edgeCur; + if (!edgeCur.IsBorder()) + { + do + { + edgeCur.NextF(); + edgeCur.F()->SetV(); + RD[edgeCur.F()].ep[edgeCur.E()] = true; + ++NewFaceNum; + } while (edgeCur != start); + --NewFaceNum; //start is counted twice (could remove the first increment above) + } + } + + } // end face loop + + if(NewVertNum ==0 ) + { + tri::Allocator :: template DeletePerFaceAttribute > (m,RD); + return false; + } + VertexIterator lastv = tri::Allocator::AddVertices(m,NewVertNum); + + // Secondo loop: We initialize a edge->vertex map + + for(fi=m.face.begin();fi!=m.face.end();++fi) if(!(*fi).IsD()) + { + if(cb && (++step%PercStep)==0)(*cb)(step/PercStep,"Refining..."); + for(j=0;j<3;j++) + { + // skip unselected faces if necessary + if(RefineSelected && !(*fi).IsS()) continue; + for(j=0;j<3;j++) + { + PosType edgeCur(&*fi,j); + if(RefineSelected && ! edgeCur.FFlip()->IsS()) continue; + + if( RD[edgeCur.F()].ep[edgeCur.E()] && RD[edgeCur.F()].vp[edgeCur.E()] ==0 ) + { + RD[edgeCur.F()].vp[edgeCur.E()] = &*lastv; + mid(*lastv,edgeCur); + PosType start = edgeCur; + if (!edgeCur.IsBorder()) + { + do + { + edgeCur.NextF(); + assert(RD[edgeCur.F()].ep[edgeCur.E()]); + RD[edgeCur.F()].vp[edgeCur.E()] = &*lastv; + } while (edgeCur != start); + } + ++lastv; + } + } + } + } + + assert(lastv==m.vert.end()); // critical assert: we MUST have used all the vertex that we forecasted we need + + FaceIterator lastf = tri::Allocator::AddFaces(m,NewFaceNum); + FaceIterator oldendf = lastf; + +/* + * v0 + * + * + * f0 + * + * mp01 f3 mp02 + * + * + * f1 f2 + * + *v1 mp12 v2 + * +*/ + + VertexPointer vv[6]; // The six vertices that arise in the single triangle splitting + // 0..2 Original triangle vertices + // 3..5 mp01, mp12, mp20 midpoints of the three edges + FacePointer nf[4]; // The (up to) four faces that are created. + + TexCoordType wtt[6]; // per ogni faccia sono al piu' tre i nuovi valori + // di texture per wedge (uno per ogni edge) + + int fca=0; + for(fi=m.face.begin();fi!=oldendf;++fi) if(!(*fi).IsD()) + { + if(cb && (++step%PercStep)==0) + (*cb)(step/PercStep,"Refining..."); + vv[0]=(*fi).V(0); + vv[1]=(*fi).V(1); + vv[2]=(*fi).V(2); + vv[3] = RD[fi].vp[0]; + vv[4] = RD[fi].vp[1]; + vv[5] = RD[fi].vp[2]; + + int ind = ((vv[3] != NULL) ? 1 : 0) + ((vv[4] != NULL) ? 2 : 0) + ((vv[5] != NULL) ? 4 : 0); + + nf[0]=&*fi; + int i; + for(i=1;iImportData(*fi); + + } + + + if(tri::HasPerWedgeTexCoord(m)) + for(i=0;i<3;++i) + { + wtt[i]=(*fi).WT(i); + wtt[3+i]=mid.WedgeInterp((*fi).WT(i),(*fi).WT((i+1)%3)); + } + + int orgflag = (*fi).Flags(); + for (i=0; iP(),vv[SplitTab[ind].swap[0][1]]->P()) < + SquaredDistance(vv[SplitTab[ind].swap[1][0]]->P(),vv[SplitTab[ind].swap[1][1]]->P()) ) + { // swap the last two triangles + (*nf[2]).V(1)=(*nf[1]).V(0); + (*nf[1]).V(1)=(*nf[2]).V(0); + if(tri::HasPerWedgeTexCoord(m)){ //swap also textures coordinates + (*nf[2]).WT(1)=(*nf[1]).WT(0); + (*nf[1]).WT(1)=(*nf[2]).WT(0); + } + + if((*nf[1]).IsB(0)) (*nf[2]).SetB(1); else (*nf[2]).ClearB(1); + if((*nf[2]).IsB(0)) (*nf[1]).SetB(1); else (*nf[1]).ClearB(1); + (*nf[1]).ClearB(0); + (*nf[2]).ClearB(0); + + if((*nf[1]).IsFaceEdgeS(0)) (*nf[2]).SetFaceEdgeS(1); else (*nf[2]).ClearFaceEdgeS(1); + if((*nf[2]).IsFaceEdgeS(0)) (*nf[1]).SetFaceEdgeS(1); else (*nf[1]).ClearFaceEdgeS(1); + (*nf[1]).ClearFaceEdgeS(0); + (*nf[2]).ClearFaceEdgeS(0); + } + } + + assert(lastf==m.face.end()); // critical assert: we MUST have used all the faces that we forecasted we need and that we previously allocated. + assert(!m.vert.empty()); + for(fi=m.face.begin();fi!=m.face.end();++fi) if(!(*fi).IsD()){ + assert((*fi).V(0)>=&*m.vert.begin() && (*fi).V(0)<=&m.vert.back() ); + assert((*fi).V(1)>=&*m.vert.begin() && (*fi).V(1)<=&m.vert.back() ); + assert((*fi).V(2)>=&*m.vert.begin() && (*fi).V(2)<=&m.vert.back() ); + } + tri::UpdateTopology::FaceFace(m); + + tri::Allocator :: template DeletePerFaceAttribute > (m,RD); + + return true; +} + } // namespace tri } // namespace vcg