From ab5869f6c3107619847c1cca54635d44f38afc5a Mon Sep 17 00:00:00 2001 From: cignoni Date: Fri, 20 Dec 2013 02:33:36 +0000 Subject: [PATCH] Added the possibility of constraining the movement of some seeds onto a specific domain. Now during relaxation you can for example fix some seeds and constrain some other seeds over linear features defined as subset of vertices. --- .../trimesh_voronoisampling.cpp | 65 ++++++++++--- vcg/complex/algorithms/voronoi_clustering.h | 91 ++++++++++++++----- 2 files changed, 119 insertions(+), 37 deletions(-) diff --git a/apps/sample/trimesh_voronoisampling/trimesh_voronoisampling.cpp b/apps/sample/trimesh_voronoisampling/trimesh_voronoisampling.cpp index 58abab24..02852cb8 100644 --- a/apps/sample/trimesh_voronoisampling/trimesh_voronoisampling.cpp +++ b/apps/sample/trimesh_voronoisampling/trimesh_voronoisampling.cpp @@ -81,15 +81,47 @@ int main( int argc, char **argv ) return -1; } + tri::Clean::RemoveUnreferencedVertex(baseMesh); + tri::Allocator::CompactEveryVector(baseMesh); tri::UpdateTopology::VertexFace(baseMesh); tri::UpdateFlags::FaceBorderFromVF(baseMesh); + tri::UpdateFlags::VertexBorderFromFace(baseMesh); - // -- Build the mesh with corners - MyMesh cornerMesh; + tri::SurfaceSampling >::PoissonDiskParam pp; + float radius = tri::SurfaceSampling >::ComputePoissonDiskRadius(baseMesh,sampleNum); + + + // -- Build a sampling with just corners (Poisson filtered) + MyMesh poissonCornerMesh; std::vector sampleVec; tri::TrivialSampler mps(sampleVec); tri::SurfaceSampling >::VertexBorderCorner(baseMesh,mps,math::ToRad(150.f)); - tri::Build(cornerMesh,sampleVec); + tri::Build(poissonCornerMesh,sampleVec); + tri::io::ExporterPLY::Save(poissonCornerMesh,"cornerMesh.ply"); + sampleVec.clear(); + tri::SurfaceSampling >::PoissonDiskPruning(mps, poissonCornerMesh, radius, pp); + tri::Build(poissonCornerMesh,sampleVec); + tri::io::ExporterPLY::Save(poissonCornerMesh,"poissonCornerMesh.ply"); + + // Now save the corner as Fixed Seeds for later... + std::vector fixedSeedVec; + tri::VoronoiProcessing::SeedToVertexConversion(baseMesh,sampleVec,fixedSeedVec); + tri::VoronoiProcessing >::FixVertexVector(baseMesh,fixedSeedVec); + + // -- Build a sampling with points on the border + MyMesh borderMesh,poissonBorderMesh; + sampleVec.clear(); + tri::SurfaceSampling >::VertexBorder(baseMesh,mps); + tri::Build(borderMesh,sampleVec); + tri::io::ExporterPLY::Save(borderMesh,"borderMesh.ply"); + + // -- and then prune the border sampling with poisson strategy using the precomputed corner vertexes. + pp.preGenMesh = &poissonCornerMesh; + pp.preGenFlag=true; + sampleVec.clear(); + tri::SurfaceSampling >::PoissonDiskPruning(mps, borderMesh, radius, pp); + tri::Build(poissonBorderMesh,sampleVec); + tri::io::ExporterPLY::Save(poissonBorderMesh,"PoissonEdgeMesh.ply"); // -- Build the montercarlo sampling of the surface MyMesh MontecarloSurfaceMesh; @@ -98,12 +130,9 @@ int main( int argc, char **argv ) tri::Build(MontecarloSurfaceMesh,sampleVec); tri::io::ExporterPLY::Save(MontecarloSurfaceMesh,"MontecarloSurfaceMesh.ply"); - // -- Prune the montecarlo sampling with poisson strategy using the precomputed corner vertexes. - tri::SurfaceSampling >::PoissonDiskParam pp; - pp.preGenMesh = &cornerMesh; - pp.preGenFlag=true; + // -- Prune the montecarlo sampling with poisson strategy using the precomputed vertexes on the border. + pp.preGenMesh = &poissonBorderMesh; sampleVec.clear(); - float radius = tri::SurfaceSampling >::ComputePoissonDiskRadius(baseMesh,sampleNum); tri::SurfaceSampling >::PoissonDiskPruning(mps, MontecarloSurfaceMesh, radius, pp); MyMesh PoissonMesh; tri::Build(PoissonMesh,sampleVec); @@ -111,26 +140,32 @@ int main( int argc, char **argv ) std::vector seedVec; tri::VoronoiProcessing::SeedToVertexConversion(baseMesh,sampleVec,seedVec); - float eps = baseMesh.bbox.Diag()/10000.0f; - for(size_t i=0;iP()) < eps) - seedVec[j]->SetS(); + + // Select all the vertexes on the border to define a constrained domain. + // In our case we select the border vertexes to make sure that the seeds on the border + // relax themselves remaining on the border + for(size_t i=0;i dd; int t0=clock(); + // And now, at last, the relaxing procedure! tri::VoronoiProcessing >::VoronoiRelaxing(baseMesh, seedVec, iterNum, dd, vpp); int t1=clock(); + + // Get the result in some pleasant form converting it to a real voronoi diagram. tri::VoronoiProcessing >::ConvertVoronoiDiagramToMesh(baseMesh,outMesh,polyMesh, seedVec, dd, vpp); tri::io::ExporterPLY::Save(baseMesh,"base.ply",tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_VERTQUALITY ); tri::io::ExporterPLY::Save(outMesh,"out.ply",tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_FLAGS ); diff --git a/vcg/complex/algorithms/voronoi_clustering.h b/vcg/complex/algorithms/voronoi_clustering.h index 78af14b5..1f66e083 100644 --- a/vcg/complex/algorithms/voronoi_clustering.h +++ b/vcg/complex/algorithms/voronoi_clustering.h @@ -65,7 +65,8 @@ struct VoronoiProcessingParameter colorStrategy = DistanceFromSeed; areaThresholdPerc=0; deleteUnreachedRegionFlag=false; - fixSelectedSeed=false; + constrainSelectedSeed=false; + preserveFixedSeed=false; collapseShortEdge=false; collapseShortEdgePerc = 0.01f; triangulateRegion=false; @@ -76,9 +77,15 @@ struct VoronoiProcessingParameter float areaThresholdPerc; bool deleteUnreachedRegionFlag; bool unbiasedSeedFlag; - bool fixSelectedSeed; /// the vertexes that are selected are used as fixed seeds: - /// They will not move during relaxing - /// and they will be always included in the final triangulation (if on a border). + bool constrainSelectedSeed; /// If true the selected vertexes define a constraining domain: + /// During relaxation all selected seeds are constrained to move + /// only on other selected vertices. + /// In this way you can constrain some seed to move only on certain + /// domains, for example moving only along some linear features + /// like border of creases. + bool preserveFixedSeed; /// If true the 'fixed' seeds are not moved during relaxation. + /// \see FixVertexVector function to see how to fix a set of seeds. + bool triangulateRegion; bool collapseShortEdge; float collapseShortEdgePerc; @@ -819,8 +826,11 @@ struct QuadricSumDistance } }; -/// Find the new position according to the geodesic rule. -/// For each region, given the frontiers, it chooses the point with the highest distance from the frontier +/// Find the new position +/// 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, std::vector &newSeeds, @@ -829,15 +839,18 @@ static void QuadricRelax(MeshType &m, std::vector &seedVec, std::v newSeeds.clear(); typename MeshType::template PerVertexAttributeHandle sources; sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); + typename MeshType::template PerVertexAttributeHandle fixed; + fixed = tri::Allocator:: template GetPerVertexAttribute (m,"fixed"); + QuadricSumDistance dz; std::vector dVec(m.vert.size(),dz); - assert((int)m.vert.size()==m.vn); for(VertexIterator vi=m.vert.begin();vi!=m.vert.end();++vi) { assert(sources[vi]!=0); int seedIndex = tri::Index(m,sources[vi]); dVec[seedIndex].AddPoint(vi->P()); } + // Search the local maxima for each region and use them as new seeds std::pair zz(std::numeric_limits::max(), static_cast(0)); std::vector< std::pair > seedMaximaVec(m.vert.size(),zz); @@ -847,22 +860,27 @@ static void QuadricRelax(MeshType &m, std::vector &seedVec, std::v int seedIndex = tri::Index(m,sources[vi]); ScalarType val = dVec[seedIndex].Eval(vi->P()); vi->Q()=val; - if(seedMaximaVec[seedIndex].first > val) + // if constrainSelectedSeed we search only among selected vertices + if(!vpp.constrainSelectedSeed || !sources[vi]->IsS() || vi->IsS()) { - seedMaximaVec[seedIndex].first = val; - seedMaximaVec[seedIndex].second = &*vi; + if(seedMaximaVec[seedIndex].first > val) + { + seedMaximaVec[seedIndex].first = val; + seedMaximaVec[seedIndex].second = &*vi; + } } } tri::UpdateColor::PerVertexQualityRamp(m); // tri::io::ExporterPLY::Save(m,"last.ply",tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_VERTQUALITY ); - // update the seedvector with the new maxima (For the vertex not selected) + // update the seedvector with the new maxima (For the vertex not fixed) for(size_t i=0;iIsS()) - newSeeds.push_back(sources[seedMaximaVec[i].second]); + VertexPointer curSrc = sources[seedMaximaVec[i].second]; + if(vpp.preserveFixedSeed && fixed[curSrc]) + newSeeds.push_back(curSrc); else newSeeds.push_back(seedMaximaVec[i].second); } @@ -878,6 +896,8 @@ static void GeodesicRelax(MeshType &m, std::vector &seedVec, std:: newSeeds.clear(); typename MeshType::template PerVertexAttributeHandle sources; sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); + typename MeshType::template PerVertexAttributeHandle fixed; + fixed = tri::Allocator:: template GetPerVertexAttribute (m,"fixed"); std::vector::VertDist> biasedFrontierVec; BuildBiasedSeedVec(m,df,seedVec,frontierVec,biasedFrontierVec,vpp); @@ -895,21 +915,29 @@ static void GeodesicRelax(MeshType &m, std::vector &seedVec, std:: { assert(sources[vi]!=0); int seedIndex = tri::Index(m,sources[vi]); - if(seedMaximaVec[seedIndex].first < (*vi).Q()) + if(!vpp.constrainSelectedSeed || !sources[vi]->IsS() || vi->IsS()) { - seedMaximaVec[seedIndex].first=(*vi).Q(); - seedMaximaVec[seedIndex].second=&*vi; + if(seedMaximaVec[seedIndex].first < (*vi).Q()) + { + seedMaximaVec[seedIndex].first=(*vi).Q(); + seedMaximaVec[seedIndex].second=&*vi; + } } } // update the seedvector with the new maxima (For the vertex not selected) for(size_t i=0;iIsS()) - newSeeds.push_back(sources[seedMaximaVec[i].second]); + VertexPointer curSrc = sources[seedMaximaVec[i].second]; + 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); } } @@ -934,18 +962,37 @@ static void PruneSeedByRegionArea(std::vector &seedVec, swap(seedVec,newSeedVec); } +/// Mark a vector of seeds to be fixed. +static void FixVertexVector(MeshType &m, std::vector &vertToFixVec) +{ + typename MeshType::template PerVertexAttributeHandle fixed; + fixed = tri::Allocator:: template GetPerVertexAttribute (m,"fixed"); + for(VertexIterator vi=m.vert.begin();vi!=m.vert.end();++vi) + fixed[vi]=false; + for(size_t i=0;i &seedVec, int relaxIter, DistanceFunctor &df, - VoronoiProcessingParameter &vpp, vcg::CallBackPos *cb=0) +static void VoronoiRelaxing(MeshType &m, std::vector &seedVec, + int relaxIter, DistanceFunctor &df, + VoronoiProcessingParameter &vpp, + vcg::CallBackPos *cb=0) { tri::RequireVFAdjacency(m); + tri::RequireCompactness(m); + for(VertexIterator vi=m.vert.begin();vi!=m.vert.end();++vi) + assert(vi->VFp()); + tri::UpdateFlags::FaceBorderFromVF(m); tri::UpdateFlags::VertexBorderFromFace(m); typename MeshType::template PerVertexAttributeHandle sources; sources = tri::Allocator:: template GetPerVertexAttribute (m,"sources"); + typename MeshType::template PerVertexAttributeHandle fixed; + fixed = tri::Allocator:: template GetPerVertexAttribute (m,"fixed"); for(int iter=0;iter