diff --git a/vcg/complex/algorithms/voronoi_clustering.h b/vcg/complex/algorithms/voronoi_clustering.h index fd44e6cc..3a51c0a4 100644 --- a/vcg/complex/algorithms/voronoi_clustering.h +++ b/vcg/complex/algorithms/voronoi_clustering.h @@ -26,6 +26,10 @@ #include #include +#include +#include + + namespace vcg { namespace tri @@ -89,6 +93,8 @@ struct VoronoiProcessingParameter bool preserveFixedSeed; /// If true the 'fixed' seeds are not moved during relaxation. /// \see FixVertexVector function to see how to fix a set of seeds. + // Convertion to Voronoi Diagram Parameters + bool triangulateRegion; /// If true when building the voronoi diagram mesh each region is a /// triangulated polygon. Otherwise it each voronoi region is a star /// triangulation with the original seed in the center. @@ -117,7 +123,7 @@ public: // Given a vector of point3f it finds the closest vertices on the mesh. -static void SeedToVertexConversion(MeshType &m,std::vector &seedPVec,std::vector &seedVVec) +static void SeedToVertexConversion(MeshType &m,std::vector &seedPVec,std::vector &seedVVec, bool compactFlag = true) { typedef typename vcg::SpatialHashTable HashVertexGrid; seedVVec.clear(); @@ -138,6 +144,12 @@ static void SeedToVertexConversion(MeshType &m,std::vector &seedPVec, seedVVec.push_back(vp); } } + if(compactFlag) + { + std::sort(seedVVec.begin(),seedVVec.end()); + typename std::vector::iterator vi = std::unique(seedVVec.begin(),seedVVec.end()); + seedVVec.resize( std::distance(seedVVec.begin(),vi) ); + } } typedef typename MeshType::template PerVertexAttributeHandle PerVertexPointerHandle; @@ -367,6 +379,7 @@ static bool isBorderCorner(FaceType *f, typename MeshType::template PerVertexAtt return false; } +// Given two supposedly adjacent border corner faces it finds the source common to them; static VertexPointer CommonSourceBetweenBorderCorner(FacePointer f0, FacePointer f1, typename MeshType::template PerVertexAttributeHandle &sources) { assert(isBorderCorner(f0,sources)); @@ -389,26 +402,45 @@ static VertexPointer CommonSourceBetweenBorderCorner(FacePointer f0, FacePointer return 0; } +/// \brief Build a mesh of voronoi diagram from the given seeds +/// +/// This function assumes that you have just run a geodesic like algorithm over your mesh using +/// a seed set as starting points and that there is an PerVertex Attribute called 'sources' +/// with pointers to the seed source. Usually you can initialize it with something like +/// +/// DistanceFunctor &df, +/// tri::Geodesic::Compute(m, seedVec, df, std::numeric_limits::max(),0,&sources); +/// + static void ConvertVoronoiDiagramToMesh(MeshType &m, MeshType &outMesh, MeshType &outPoly, std::vector &seedVec, - DistanceFunctor &df, VoronoiProcessingParameter &vpp ) + VoronoiProcessingParameter &vpp ) { + tri::RequirePerVertexAttribute(m,"sources"); typename MeshType::template PerVertexAttributeHandle sources; sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); - tri::Geodesic::Compute(m,seedVec, df,std::numeric_limits::max(),0,&sources); outMesh.Clear(); outPoly.Clear(); tri::UpdateTopology::FaceFace(m); tri::UpdateFlags::FaceBorderFromFF(m); - std::map seedMap; // It says if a given vertex of m is a seed (and what) + std::map seedMap; // It says if a given vertex of m is a seed (and what position it has in the seed vector) for(size_t i=0;i=0) && (ind innerCornerVec, // Faces adjacent to three different regions borderCornerVec; // Faces that are on the border and adjacent to at least two regions. GetFaceCornerVec(m, sources, innerCornerVec, borderCornerVec); @@ -450,6 +482,8 @@ static void ConvertVoronoiDiagramToMesh(MeshType &m, { VertexPointer v0 = sources[innerCornerVec[i]->V0(j)]; VertexPointer v1 = sources[innerCornerVec[i]->V1(j)]; + assert(seedMap[v0]>=0);assert(seedMap[v1]>=0); + if(v1::FaceFace(outMesh); tri::Clean::OrientCoherentlyMesh(outMesh,oriented,orientable); - assert(orientable); +// assert(orientable); // check that the normal of the input mesh are consistent with the result tri::UpdateNormal::PerVertexNormalizedPerFaceNormalized(outMesh); tri::UpdateNormal::PerVertexNormalizedPerFaceNormalized(m); @@ -598,7 +632,7 @@ static void ConvertVoronoiDiagramToMesh(MeshType &m, // ******************* star to tri conversion ********* if(vpp.triangulateRegion) { - printf("Seedvec.size %i\n",seedVec.size()); +// printf("Seedvec.size %i\n",seedVec.size()); for(FaceIterator fi=outMesh.face.begin();fi!=outMesh.face.end();++fi) if(!fi->IsD()) { for(int i=0;i<3;++i) @@ -839,13 +873,18 @@ struct QuadricSumDistance } }; -/// Find the new position +/// \brief Relax the seeds of a Voronoi diagram according to the quadric distance rule. +/// /// For each region it search the vertex that minimize the sum of the squared distance /// from all the points of the region. -/// It uses a vector of QuadricSumDistances -/// (for simplicity of the size of the vertex but only the ones of the seed are used). /// -static void QuadricRelax(MeshType &m, std::vector &seedVec, std::vector &frontierVec, +/// It uses a vector of QuadricSumDistances; +/// for simplicity it is sized as the vertex vector even if only the ones of the quadric +/// corresponding to seeds are actually used. +/// +/// It return true if at least one seed changed position. +/// +static bool QuadricRelax(MeshType &m, std::vector &seedVec, std::vector &frontierVec, std::vector &newSeeds, DistanceFunctor &df, VoronoiProcessingParameter &vpp) { @@ -886,25 +925,36 @@ static void QuadricRelax(MeshType &m, std::vector &seedVec, std::v tri::UpdateColor::PerVertexQualityRamp(m); // tri::io::ExporterPLY::Save(m,"last.ply",tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_VERTQUALITY ); - + bool seedChanged=false; // update the seedvector with the new maxima (For the vertex not fixed) for(size_t i=0;i &seedVec, std::vector &frontierVec, +/// For each region, given the frontiers, it chooses the point with the highest distance from the frontier +/// This strategy automatically moves the vertices onto the boundary (if any). +/// +/// It return true if at least one seed changed position. +/// + +static bool GeodesicRelax(MeshType &m, std::vector &seedVec, std::vector &frontierVec, std::vector &newSeeds, - DistanceFunctor &df, VoronoiProcessingParameter &vpp) + DistanceFunctor &df, VoronoiProcessingParameter &vpp) { newSeeds.clear(); typename MeshType::template PerVertexAttributeHandle sources; @@ -928,6 +978,7 @@ static void GeodesicRelax(MeshType &m, std::vector &seedVec, std:: { assert(sources[vi]!=0); int seedIndex = tri::Index(m,sources[vi]); + if(!vpp.constrainSelectedSeed || !sources[vi]->IsS() || vi->IsS()) { if(seedMaximaVec[seedIndex].first < (*vi).Q()) @@ -938,6 +989,8 @@ static void GeodesicRelax(MeshType &m, std::vector &seedVec, std:: } } + bool seedChanged=false; + // update the seedvector with the new maxima (For the vertex not selected) for(size_t i=0;i &seedVec, std:: if(vpp.preserveFixedSeed && fixed[curSrc]) newSeeds.push_back(curSrc); else + { newSeeds.push_back(seedMaximaVec[i].second); -// if(vpp.fixSelectedSeed && sources[seedMaximaVec[i].second]->IsS()) -// newSeeds.push_back(sources[seedMaximaVec[i].second]); -// else -// newSeeds.push_back(seedMaximaVec[i].second); + if(curSrc != seedMaximaVec[i].second) seedChanged=true; + } } + return seedChanged; } static void PruneSeedByRegionArea(std::vector &seedVec, @@ -990,7 +1043,7 @@ static void FixVertexVector(MeshType &m, std::vector &vertToFixVec /// /// -static void VoronoiRelaxing(MeshType &m, std::vector &seedVec, +static int VoronoiRelaxing(MeshType &m, std::vector &seedVec, int relaxIter, DistanceFunctor &df, VoronoiProcessingParameter &vpp, vcg::CallBackPos *cb=0) @@ -998,7 +1051,7 @@ static void VoronoiRelaxing(MeshType &m, std::vector &seedVec, tri::RequireVFAdjacency(m); tri::RequireCompactness(m); for(VertexIterator vi=m.vert.begin();vi!=m.vert.end();++vi) - assert(vi->VFp()); + assert(vi->VFp() && "Require mesh without unreferenced vertexes\n"); tri::UpdateFlags::FaceBorderFromVF(m); tri::UpdateFlags::VertexBorderFromFace(m); @@ -1033,10 +1086,11 @@ static void VoronoiRelaxing(MeshType &m, std::vector &seedVec, if(cb) cb(iter*100/relaxIter,"Voronoi Lloyd Relaxation: Searching New Seeds"); std::vector newSeedVec; + bool changed; if(vpp.geodesicRelaxFlag) - GeodesicRelax(m,seedVec, frontierVec, newSeedVec, df,vpp); + changed = GeodesicRelax(m,seedVec, frontierVec, newSeedVec, df,vpp); else - QuadricRelax(m,seedVec,frontierVec, newSeedVec, df,vpp); + changed = QuadricRelax(m,seedVec,frontierVec, newSeedVec, df,vpp); assert(newSeedVec.size() == seedVec.size()); PruneSeedByRegionArea(newSeedVec,regionArea,vpp); @@ -1049,7 +1103,9 @@ static void VoronoiRelaxing(MeshType &m, std::vector &seedVec, newSeedVec[i]->C() = Color4b::White; swap(newSeedVec,seedVec); + if(!changed) relaxIter = iter; } + return relaxIter; } @@ -1087,24 +1143,173 @@ static void TopologicalVertexColoring(MeshType &m, std::vector &se } -static void ConvertDelaunayTriangulationToMesh(MeshType &m, - MeshType &outMesh, - std::vector &seedVec, - DistanceFunctor &df, VoronoiProcessingParameter &vpp ) +template +static std::pair ordered_pair(const genericType &a, const genericType &b) +{ + if(a, VertexPointer > &midMap) { typename MeshType::template PerVertexAttributeHandle sources; sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); - tri::Geodesic::Compute(m,seedVec, df,std::numeric_limits::max(),0,&sources); + for(FaceIterator fi = m.face.begin(); fi!=m.face.end(); ++fi) + { + VertexPointer vp[3],sp[3]; + vp[0] = (*fi).V(0); vp[1] = (*fi).V(1); vp[2] = (*fi).V(2); + sp[0] = sources[vp[0]]; sp[1] = sources[vp[1]]; sp[2] = sources[vp[2]]; + if((sp[0] == sp[1]) && (sp[0] == sp[2])) continue; // skip internal faces + + for(int i=0;i<3;++i) // for each edge of a frontier face + { + int i0 = i; + int i1 = (i+1)%3; + + VertexPointer closestVert = (*fi).V(i0); + if( (*fi).V(i1)->Q() < closestVert->Q()) closestVert = (*fi).V(i1); + + if(midMap[ordered_pair(sp[i0],sp[i1])] == 0 ) { + midMap[ordered_pair(sp[i0],sp[i1])] = closestVert; + } + else { + if(closestVert->Q() < midMap[ordered_pair(sp[i0],sp[i1])]->Q()) + midMap[ordered_pair(sp[i0],sp[i1])] = closestVert; + } + } + } +} + +/// \brief Check the topological correcteness of the induced Voronoi diagram +/// +/// This function assumes that you have just run a geodesic like algorithm over your mesh using +/// a seed set as starting points and that there is an PerVertex Attribute called 'sources' +/// with pointers to the seed source. Usually you can initialize it with something like +/// +/// DistanceFunctor &df, +/// tri::Geodesic::Compute(m, seedVec, df, std::numeric_limits::max(),0,&sources); + +static bool CheckVoronoiTopology(MeshType& m,std::vector &seedVec) +{ + tri::RequirePerVertexAttribute(m,"sources"); + tri::RequireCompactness(m); + + typename MeshType::template PerVertexAttributeHandle sources; + sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); + + std::map seedMap; // It says if a given vertex of m is a seed (and its index in seedVec) + BuildSeedMap(m,seedVec,seedMap); + + std::vector regionVec(seedVec.size(),0); + for(int i=0; i< seedVec.size();i++) regionVec[i] = new MeshType; + + for(int i=0;i::AddFace(*regionVec[vi0], m.face[i].cP(0),m.face[i].cP(1),m.face[i].cP(2)); + + if(vi1 != vi0) + tri::Allocator::AddFace(*regionVec[vi1], m.face[i].cP(0),m.face[i].cP(1),m.face[i].cP(2)); + + if((vi2 != vi0) && (vi2 != vi1) ) + tri::Allocator::AddFace(*regionVec[vi2], m.face[i].cP(0),m.face[i].cP(1),m.face[i].cP(2)); + } + + bool AllDiskRegion=true; + for(int i=0; i< seedVec.size();i++) + { + MeshType &rm = *(regionVec[i]); + tri::Clean::RemoveDuplicateVertex(rm); + tri::Allocator::CompactEveryVector(rm); + tri::UpdateTopology::FaceFace(rm); + // char buf[100]; sprintf(buf,"disk%04i.ply",i); tri::io::ExporterPLY::Save(rm,buf,tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_VERTQUALITY ); + + int NNmanifoldE=tri::Clean::CountNonManifoldEdgeFF(rm); + if (NNmanifoldE!=0) + AllDiskRegion= false; + int G=tri::Clean::MeshGenus(rm); + int numholes=tri::Clean::CountHoles(rm); + if (numholes!=1) + AllDiskRegion= false; + if(G!=0) AllDiskRegion= false; + delete regionVec[i]; + } + + if(!AllDiskRegion) return false; + + // **** Final step build a rough delaunay tri and check that it is manifold + MeshType delaMesh; + std::vector innerCornerVec, // Faces adjacent to three different regions + borderCornerVec; // Faces that are on the border and adjacent to at least two regions. + GetFaceCornerVec(m, sources, innerCornerVec, borderCornerVec); + + // First add all the needed vertices: seeds and corners + for(size_t i=0;i::AddVertex(delaMesh, seedVec[i]->P()); + + // Now just add one face for each inner corner + for(size_t i=0;iV(0)]]]; + VertexPointer v1 = & delaMesh.vert[seedMap[sources[innerCornerVec[i]->V(1)]]]; + VertexPointer v2 = & delaMesh.vert[seedMap[sources[innerCornerVec[i]->V(2)]]]; + tri::Allocator::AddFace(delaMesh,v0,v1,v2); + } + Clean::RemoveUnreferencedVertex(delaMesh); + tri::Allocator::CompactVertexVector(delaMesh); + tri::UpdateTopology::FaceFace(delaMesh); + + int nonManif = tri::Clean::CountNonManifoldEdgeFF(delaMesh); + if(nonManif>0) return false; + + return true; +} + +static void BuildSeedMap(MeshType &m, std::vector &seedVec, std::map &seedMap) +{ + seedMap.clear(); + for(size_t i=0;i::Compute(m, seedVec, df, std::numeric_limits::max(),0,&sources); +/// +/// The function can also +static void ConvertDelaunayTriangulationToMesh(MeshType &m, + MeshType &outMesh, + std::vector &seedVec, bool refineFlag=true) +{ + tri::RequirePerVertexAttribute(m,"sources"); + tri::RequireCompactness(m); + tri::RequireVFAdjacency(m); + + typename MeshType::template PerVertexAttributeHandle sources; + sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); + outMesh.Clear(); tri::UpdateTopology::FaceFace(m); tri::UpdateFlags::FaceBorderFromFF(m); std::map seedMap; // It says if a given vertex of m is a seed (and its index in seedVec) - for(size_t i=0;i innerCornerVec, // Faces adjacent to three different regions borderCornerVec; // Faces that are on the border and adjacent to at least two regions. @@ -1114,14 +1319,170 @@ static void ConvertDelaunayTriangulationToMesh(MeshType &m, for(size_t i=0;i::AddVertex(outMesh, seedVec[i]->P(),Color4b::White); - // Now just add a face for each inner corner + map, int > midMapInd; + + // Given a pair of sources gives the index of the mid vertex + map, VertexPointer > midMapPt; + if(refineFlag) + { + GenerateMidPointMap(m, midMapPt); + typename std::map, VertexPointer >::iterator mi; + for(mi=midMapPt.begin(); mi!=midMapPt.end(); ++mi) + { + midMapInd[ordered_pair(mi->first.first, mi->first.second)]=outMesh.vert.size(); + tri::Allocator::AddVertex(outMesh, mi->second->cP(), Color4b::LightBlue); + } + } + + // Now just add one (or four) face for each inner corner for(size_t i=0;iV(0)]]]; - VertexPointer v1 = & outMesh.vert[seedMap[sources[innerCornerVec[i]->V(1)]]]; - VertexPointer v2 = & outMesh.vert[seedMap[sources[innerCornerVec[i]->V(2)]]]; - tri::Allocator::AddFace(outMesh,v0,v1,v2); + VertexPointer s0 = sources[innerCornerVec[i]->V(0)]; + VertexPointer s1 = sources[innerCornerVec[i]->V(1)]; + VertexPointer s2 = sources[innerCornerVec[i]->V(2)]; + assert ( (s0!=s1) && (s0!=s2) && (s1!=s2) ); + VertexPointer v0 = & outMesh.vert[seedMap[s0]]; + VertexPointer v1 = & outMesh.vert[seedMap[s1]]; + VertexPointer v2 = & outMesh.vert[seedMap[s2]]; + if(refineFlag) + { + VertexPointer mp01 = & outMesh.vert[ midMapInd[ordered_pair(s0,s1)]]; + VertexPointer mp02 = & outMesh.vert[ midMapInd[ordered_pair(s0,s2)]]; + VertexPointer mp12 = & outMesh.vert[ midMapInd[ordered_pair(s1,s2)]]; + assert ( (mp01!=mp02) && (mp01!=mp12) && (mp02!=mp12) ); + tri::Allocator::AddFace(outMesh,v0,mp01,mp02); + tri::Allocator::AddFace(outMesh,v1,mp12,mp01); + tri::Allocator::AddFace(outMesh,v2,mp02,mp12); + tri::Allocator::AddFace(outMesh,mp01,mp12,mp02); + } + else + tri::Allocator::AddFace(outMesh,v0,v1,v2); } + Clean::RemoveUnreferencedVertex(outMesh); + tri::Allocator::CompactVertexVector(outMesh); +} + +static void PreprocessForVoronoi(MeshType &m, float radius) +{ + const int maxSubDiv = 10; + tri::RequireFFAdjacency(m); + tri::UpdateTopology::FaceFace(m); + tri::Clean::RemoveUnreferencedVertex(m); + ScalarType edgeLen = tri::Stat::ComputeFaceEdgeLengthAverage(m); + + for(int i=0;i >(m,tri::MidPoint(&m),min(edgeLen*2.0f,radius/5.0f)); + if(!ret) break; + } + tri::Allocator::CompactEveryVector(m); + tri::UpdateTopology::VertexFace(m); +} + +static void RelaxRefineTriangulationSpring(MeshType &m, MeshType &delaMesh, int refineStep=3, int relaxStep=10 ) +{ + tri::RequireCompactness(m); + tri::RequireCompactness(delaMesh); + tri::RequireVFAdjacency(delaMesh); + tri::RequireFFAdjacency(delaMesh); + tri::RequirePerFaceMark(delaMesh); + + const float convergenceThr = 0.001f; + const float eulerStep = 0.1f; + + tri::UpdateNormal::PerVertexNormalizedPerFaceNormalized(m); + typedef GridStaticPtr TriMeshGrid; + TriMeshGrid ug; + ug.Set(m.face.begin(),m.face.end()); + + const ScalarType maxDist = m.bbox.Diag()/4.f; + for(int kk=0;kk::FaceFace(delaMesh); + + if(kk!=0) // first step do not refine; + { + int nonManif = tri::Clean::CountNonManifoldEdgeFF(delaMesh); + if(nonManif) return; + tri::Refine >(delaMesh,tri::MidPoint(&delaMesh)); + } + tri::UpdateTopology::VertexFace(delaMesh); + + for(int k=0;k avgForce(delaMesh.vn); + std::vector avgLenVec(delaMesh.vn,0); + for(int i=0;i starVec; + face::VVStarVF(&delaMesh.vert[i],starVec); + + for(int j=0;jcP()); + avgLenVec[i] /= float(starVec.size()); + + avgForce[i] =Point3f(0,0,0); + for(int j=0;jcP(); + float len = force.Norm(); + force.Normalize(); + avgForce[i] += force * (avgLenVec[i]-len); + } + } + bool changed=false; + for(int i=0;i avgLenVec[i]*convergenceThr) changed = true; + delaMesh.vert[i].P()=closest; + } + if(!changed) k=relaxStep; + } + } +} + +static void RelaxRefineTriangulationLaplacian(MeshType &m, MeshType &delaMesh, int refineStep=3, int relaxStep=10 ) +{ + tri::RequireCompactness(m); + tri::RequireCompactness(delaMesh); + tri::RequireFFAdjacency(delaMesh); + tri::RequirePerFaceMark(delaMesh); + tri::UpdateTopology::FaceFace(delaMesh); + + typedef GridStaticPtr TriMeshGrid; + TriMeshGrid ug; + ug.Set(m.face.begin(),m.face.end()); + const ScalarType maxDist = m.bbox.Diag()/4.f; + + int origVertNum = delaMesh.vn; + + for(int k=0;k::VertexClear(delaMesh); + + tri::Refine >(delaMesh,tri::MidPoint(&delaMesh)); + + for(int j=0;j::VertexCoordLaplacian(delaMesh,1,true); + for(int i=origVertNum;i::VertexCoordLaplacianBlend(delaMesh,1,0.2f,true); + } + } + for(int i=origVertNum;i