diff --git a/apps/sample/trimesh_topological_cut/trimesh_topological_cut.cpp b/apps/sample/trimesh_topological_cut/trimesh_topological_cut.cpp index 3bb5daac..8584e585 100644 --- a/apps/sample/trimesh_topological_cut/trimesh_topological_cut.cpp +++ b/apps/sample/trimesh_topological_cut/trimesh_topological_cut.cpp @@ -39,57 +39,63 @@ struct MyUsedTypes : public UsedTypes::AsVertexType, Use:: class MyVertex : public Vertex< MyUsedTypes, vertex::Coord3f, vertex::Normal3f, vertex::Qualityf, vertex::Color4b, vertex::VEAdj, vertex::VFAdj,vertex::BitFlags >{}; class MyEdge : public Edge< MyUsedTypes, edge::VertexRef, edge::VEAdj, edge::EEAdj, edge::BitFlags> {}; -class MyFace : public Face < MyUsedTypes, face::VertexRef, face::Normal3f, face::VFAdj, face::FFAdj, face::Mark, face::Color4b, face::BitFlags > {}; +class MyFace : public Face < MyUsedTypes, face::VertexRef, face::Normal3f, face::Qualityf, face::Color4b, face::VFAdj, face::FFAdj, face::Mark, face::Color4b, face::BitFlags > {}; class MyMesh : public tri::TriMesh< std::vector, std::vector, std::vector >{}; - +/** + * In this sample we take a torus we compute a poly line on it that open it into a disk and we open it. + * Then using the COM (Curve On Manifold) framework we smooth this polyline keeping + * it on the surface of the torus and then first we refine the torus surface with this + * smooth polyline and then we open it along these new edges. + * + * Optionally you can use your own mesh and polyline by passing them as parameters. + */ int main(int argc,char ** argv ) { MyMesh base, basecopy, poly; - int ret0 = tri::io::Importer::Open(base,argv[1]); - int ret1 = 0; - if(argc>2) ret1 = tri::io::Importer::Open(poly,argv[2]); - if(ret0 != 0 || ret1 != 0) - { - printf("Failed Loading\n"); - exit(-1); - } + int ret0=0, ret1=0; + if(argc>1) ret0 = tri::io::Importer::Open(base,argv[1]); + if(base.FN() == 0) Torus(base,10,4,48,24); + + if(argc>2) ret1 = tri::io::Importer::Open(poly,argv[2]); tri::UpdateBounding::Box(base); printf( "Mesh %s has %i vert and %i faces\n", argv[1], base.VN(), base.FN() ); printf( "Poly %s has %i vert and %i edges\n", argv[2], poly.VN(), poly.EN() ); - if(poly.EN()==0) { - srand(time(0)); + if(poly.EN() == 0) { + srand(time(nullptr)); tri::CutTree ct(base); - ct.BuildVisitTree(poly,rand()%base.fn); + ct.Build(poly,rand()%base.fn); } tri::io::ExporterPLY::Save(poly,"0_cut_tree.ply",tri::io::Mask::IOM_EDGEINDEX); - + tri::CoM cc(base); cc.Init(); - cc.MarkFauxEdgeWithPolyLine(poly); - tri::Append::MeshCopy(basecopy,base); - tri::UpdateTopology::FaceFace(basecopy); - tri::CutMeshAlongNonFauxEdges(basecopy); - tri::io::ExporterPLY::Save(basecopy,"base_cut_with_tree.ply"); - + bool ret = cc.TagFaceEdgeSelWithPolyLine(poly); + if(ret) + { + tri::Append::MeshCopy(basecopy,base); + tri::UpdateTopology::FaceFace(basecopy); + tri::CutMeshAlongSelectedFaceEdges(basecopy); + tri::io::ExporterPLY::Save(basecopy,"base_cut_with_tree.ply"); + } // Selected vertices are 'locked' during the smoothing. cc.SelectBoundaryVertex(poly); - cc.SelectUniformlyDistributed(poly,20); // lock some vertices uniformly just for fun +// cc.SelectUniformlyDistributed(poly,10); // lock some vertices uniformly just for fun // Two smoothing runs, // the first that allows fast movement over the surface (long edges that can skim surface details) - cc.par.surfDistThr = base.bbox.Diag()/100.0; - cc.par.maxSimpEdgeLen = base.bbox.Diag()/50.0; - cc.par.minRefEdgeLen = base.bbox.Diag()/100.0; - cc.SmoothProject(poly,10,0.7,.3); + cc.par.surfDistThr = base.bbox.Diag()/100.0f; + cc.par.maxSimpEdgeLen = base.bbox.Diag()/50.0f; + cc.par.minRefEdgeLen = base.bbox.Diag()/100.0f; + cc.SmoothProject(poly,30,0.7f,.3f); tri::io::ExporterPLY::Save(poly,"1_poly_smooth.ply",tri::io::Mask::IOM_EDGEINDEX+tri::io::Mask::IOM_VERTCOLOR+tri::io::Mask::IOM_VERTQUALITY); // The second smooting run more accurate to adapt to the surface - cc.par.surfDistThr = base.bbox.Diag()/1000.0; - cc.par.maxSimpEdgeLen = base.bbox.Diag()/1000.0; - cc.par.minRefEdgeLen = base.bbox.Diag()/2000.0; - cc.SmoothProject(poly,10,0.01,.99); + cc.par.surfDistThr = base.bbox.Diag()/1000.0f; + cc.par.maxSimpEdgeLen = base.bbox.Diag()/1000.0f; + cc.par.minRefEdgeLen = base.bbox.Diag()/2000.0f; + cc.SmoothProject(poly,10,0.01f,.99f); tri::io::ExporterPLY::Save(poly,"2_poly_smooth.ply",tri::io::Mask::IOM_EDGEINDEX+tri::io::Mask::IOM_VERTCOLOR+tri::io::Mask::IOM_VERTQUALITY); Distribution dist; @@ -102,8 +108,8 @@ int main(int argc,char ** argv ) cc.SplitMeshWithPolyline(poly); tri::io::ExporterPLY::Save(base,"3_mesh_refined.ply",tri::io::Mask::IOM_VERTCOLOR+tri::io::Mask::IOM_VERTQUALITY); // Now the two meshes should have coincident edges - cc.MarkFauxEdgeWithPolyLine(poly); - CutMeshAlongNonFauxEdges(base); + cc.TagFaceEdgeSelWithPolyLine(poly); + CutMeshAlongSelectedFaceEdges(base); tri::io::ExporterPLY::Save(base,"4_mesh_cut.ply",tri::io::Mask::IOM_VERTCOLOR+tri::io::Mask::IOM_VERTQUALITY); return 0; diff --git a/vcg/complex/algorithms/curve_on_manifold.h b/vcg/complex/algorithms/curve_on_manifold.h index 9ea56889..1789b841 100644 --- a/vcg/complex/algorithms/curve_on_manifold.h +++ b/vcg/complex/algorithms/curve_on_manifold.h @@ -184,9 +184,9 @@ public: * */ - bool MarkFauxEdgeWithPolyLine(MeshType &poly,bool markFlag=true) + bool TagFaceEdgeSelWithPolyLine(MeshType &poly,bool markFlag=true) { - if(markFlag) tri::UpdateFlags::FaceSetF(base); + if(markFlag) tri::UpdateFlags::FaceClearFaceEdgeS(base); tri::UpdateTopology::VertexFace(base); tri::UpdateTopology::FaceFace(base); @@ -210,8 +210,8 @@ public: if(ret){ assert(ret); assert(ff0->V(e0)==v0 || ff0->V(e0)==v1); - ff0->ClearF(e0); - ff1->ClearF(e1); + ff0->SetFaceEdgeS(e0); + ff1->SetFaceEdgeS(e1); } else { return false; diff --git a/vcg/complex/algorithms/cut_tree.h b/vcg/complex/algorithms/cut_tree.h index 05c66e38..325e70a7 100644 --- a/vcg/complex/algorithms/cut_tree.h +++ b/vcg/complex/algorithms/cut_tree.h @@ -23,6 +23,11 @@ #ifndef CUT_TREE_H #define CUT_TREE_H +#include +#include +#include +#include + namespace vcg { namespace tri { @@ -40,19 +45,18 @@ public: typedef typename MeshType::FaceType FaceType; typedef typename MeshType::FacePointer FacePointer; typedef typename MeshType::FaceIterator FaceIterator; - typedef Box3 Box3Type; - typedef typename vcg::GridStaticPtr MeshGrid; - typedef typename vcg::GridStaticPtr EdgeGrid; + typedef Box3 Box3Type; typedef typename face::Pos PosType; typedef typename tri::UpdateTopology::PEdge PEdge; MeshType &base; -// MeshGrid uniformGrid; -// Param par; CutTree(MeshType &_m) :base(_m){} +// Perform a simple optimization of the three applying simple shortcuts: +// if the endpoints of two consecutive edges are connected by an edge existing on base mesh just use that edges + void OptimizeTree(KdTree &kdtree, MeshType &t) { tri::Allocator::CompactEveryVector(t); @@ -67,7 +71,7 @@ void OptimizeTree(KdTree &kdtree, MeshType &t) { std::vector starVec; edge::VVStarVE(&*vi,starVec); - if(starVec.size()==2) + if(starVec.size()==2) // middle vertex has to be 1-manifold { PosType pos; if(ExistEdge(kdtree,starVec[0]->P(),starVec[1]->P(),pos)) @@ -181,10 +185,10 @@ void Retract(KdTree &kdtree, MeshType &t) if(fpos.IsBorder()) { t.edge[i].SetV(); } - } + } else assert(0); } - + // All the boundary edges are in the initial tree so the clean boundary loops chains remains as irreducible loops // We delete them (leaving dangling edges with a vertex on the boundary) for(size_t i =0; i &kdtree, MeshType &t) tri::Allocator::CompactEveryVector(t); } +/** \brief Main function + * + * It builds a cut tree that open the mesh into a topological disk + * + * + */ +void Build(MeshType &dualMesh, int startingFaceInd=0) +{ + tri::UpdateTopology::FaceFace(base); + tri::UpdateTopology::VertexFace(base); + + BuildVisitTree(dualMesh,startingFaceInd); +// BuildDijkstraVisitTree(dualMesh,startingFaceInd); + + VertexConstDataWrapper vdw(base); + KdTree kdtree(vdw); + Retract(kdtree,dualMesh); + OptimizeTree(kdtree, dualMesh); + tri::UpdateBounding::Box(dualMesh); +} + +/* Auxiliary class for keeping the heap of vertices to visit and their estimated distance */ + struct FaceDist{ + FaceDist(FacePointer _f):f(_f),dist(_f->Q()){} + FacePointer f; + ScalarType dist; + bool operator < (const FaceDist &o) const + { + if( dist != o.dist) + return dist > o.dist; + return f::max()) +{ + tri::RequireFFAdjacency(base); + tri::RequirePerFaceMark(base); + tri::RequirePerFaceQuality(base); + typename MeshType::template PerFaceAttributeHandle parentHandle + = tri::Allocator::template GetPerFaceAttribute(base, "parent"); + + std::vector seedVec; + seedVec.push_back(&base.face[startingFaceInd]); + + std::vector Heap; + tri::UnMarkAll(base); + tri::UpdateQuality::FaceConstant(base,0); + ForEachVertex(base, [&](VertexType &v){ + tri::Allocator::AddVertex(dualMesh,v.cP()); + }); + + // Initialize the face heap; + // All faces in the heap are already marked; Q() store the distance from the source faces; + for(size_t i=0;iQ()=0; + Heap.push_back(FaceDist(seedVec[i])); + } + // Main Loop + int boundary=0; + std::make_heap(Heap.begin(),Heap.end()); + + int vCnt=0; + int eCnt=0; + int fCnt=0; + + // The main idea is that in the heap we maintain all the faces to be visited. + int nonDiskCnt=0; + while(!Heap.empty() && nonDiskCnt<10) + { + int eulerChi= vCnt-eCnt+fCnt; + if(eulerChi==1) nonDiskCnt=0; + else ++nonDiskCnt; +// printf("HeapSize %i: %i - %i + %i = %i\n",Heap.size(), vCnt,eCnt,fCnt,eulerChi); + pop_heap(Heap.begin(),Heap.end()); + FacePointer currFp = (Heap.back()).f; + if(tri::IsMarked(base,currFp)) + { +// printf("Found an already visited face %f %f \n",Heap.back().dist, Heap.back().f->Q()); + //assert(Heap.back().dist != currFp->Q()); + + Heap.pop_back(); + continue; + } + Heap.pop_back(); + ++fCnt; + eCnt+=3; + tri::Mark(base,currFp); + +// printf("pop face %i \n", tri::Index(base,currFp)); + for(int i=0;i<3;++i) + { + if(!currFp->V(i)->IsV()) {++vCnt; currFp->V(i)->SetV();} + + FacePointer nextFp = currFp->FFp(i); + if( tri::IsMarked(base,nextFp) ) + { + eCnt-=1; + printf("is marked\n"); + if(nextFp != parentHandle[currFp] ) + { + if(currFp>nextFp){ + tri::Allocator::AddEdge(dualMesh,tri::Index(base,currFp->V0(i)), tri::Index(base,currFp->V1(i))); + } + } + } + else // add it to the heap; + { +// printf("is NOT marked\n"); + parentHandle[nextFp] = currFp; + ScalarType nextDist = currFp->Q() + Distance(Barycenter(*currFp),Barycenter(*nextFp)); + int adjMarkedNum=0; + for(int k=0;k<3;++k) if(tri::IsMarked(base,nextFp->FFp(k))) ++adjMarkedNum; + if(nextDist < maxDistanceThr || adjMarkedNum>1) + { + nextFp->Q() = nextDist; + Heap.push_back(FaceDist(nextFp)); + push_heap(Heap.begin(),Heap.end()); + } + else { +// printf("boundary %i\n",++boundary); + tri::Allocator::AddEdge(dualMesh,tri::Index(base,currFp->V0(i)), tri::Index(base,currFp->V1(i))); + } + } + } + } // End while + printf("fulltree %i vn %i en \n",dualMesh.vn, dualMesh.en); + int dupVert=tri::Clean::RemoveDuplicateVertex(dualMesh,false); printf("Removed %i dup vert\n",dupVert); + int dupEdge=tri::Clean::RemoveDuplicateEdge(dualMesh); printf("Removed %i dup edges %i\n",dupEdge,dualMesh.EN()); + tri::Clean::RemoveUnreferencedVertex(dualMesh); + + tri::io::ExporterPLY::Save(dualMesh,"fulltree.ply",tri::io::Mask::IOM_EDGEINDEX); + tri::UpdateColor::PerFaceQualityRamp(base); + tri::io::ExporterPLY::Save(base,"colored_Bydistance.ply",tri::io::Mask::IOM_FACECOLOR); +} // \brief This function build a cut tree. // @@ -207,9 +348,6 @@ void Retract(KdTree &kdtree, MeshType &t) void BuildVisitTree(MeshType &dualMesh, int startingFaceInd=0) { - tri::UpdateTopology::FaceFace(base); - tri::UpdateTopology::VertexFace(base); - tri::UpdateFlags::FaceClearV(base); tri::UpdateFlags::VertexBorderFromFaceAdj(base); std::vector > visitStack; // the stack contain the pos on the 'starting' face. @@ -243,16 +381,9 @@ void BuildVisitTree(MeshType &dualMesh, int startingFaceInd=0) } } assert(cnt==base.fn); - - VertexConstDataWrapper vdw(base); - KdTree kdtree(vdw); - + tri::Clean::RemoveDuplicateVertex(dualMesh); -// tri::io::ExporterPLY::Save(dualMesh,"fulltree.ply",tri::io::Mask::IOM_EDGEINDEX); - - Retract(kdtree,dualMesh); - OptimizeTree(kdtree, dualMesh); - tri::UpdateBounding::Box(dualMesh); + tri::io::ExporterPLY::Save(dualMesh,"fulltree.ply",tri::io::Mask::IOM_EDGEINDEX); } };