/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2004-2016 \/)\/ * * 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 _VCGLIB_VORONOI_REMESHER_H #define _VCGLIB_VORONOI_REMESHER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_VORO 1 #include #include namespace vcg { namespace tri { template class Remesher { public: typedef Remesher ThisType; typedef MeshType Mesh; typedef typename Mesh::ScalarType ScalarType; typedef typename Mesh::CoordType CoordType; typedef typename Mesh::FaceType FaceType; typedef typename Mesh::FacePointer FacePointer; typedef typename Mesh::VertexType VertexType; typedef typename Mesh::VertexPointer VertexPointer; typedef typename Mesh::FaceIterator FaceIterator; typedef typename Mesh::VertexIterator VertexIterator; typedef std::shared_ptr MeshPtr; protected: typedef face::Pos PosType; /// \brief splitCC split the provided mesh into connected components. /// \param mesh the inputMesh. /// \return the vector of connected components (meshes) for the input model /// (if the input mesh is a single connected component returns an empty vector). /// inline static std::vector splitCC(MeshType & mesh) { std::vector ret; // find the connected components std::vector > CCV; Clean::ConnectedComponents(mesh, CCV); if (CCV.size() == 1) return ret; for(size_t i=0; i::Clear(mesh); CCV[i].second->SetS(); UpdateSelection::FaceConnectedFF(mesh); ret.push_back(std::make_shared()); Append::MeshCopy(*(ret.back()), mesh, true); } return ret; } public: static const int VoroRelaxationStep = 20; /// /// \brief Remesh the main function that remeshes a mesh preserving creases. /// \param original the mesh /// \param samplingRadius is the sampling ragius for remeshing /// \param borderCreaseAngleThreshold is the angle treshold for preserving corner points on the mesh boundary /// \return the remeshed mesh /// static inline MeshPtr Remesh(Mesh & original, const ScalarType samplingRadius, const ScalarType borderCreaseAngleThreshold = 0.0) { UpdateTopology::FaceFace(original); UpdateFlags::FaceBorderFromFF(original); UpdateFlags::VertexBorderFromFaceAdj(original); if (Clean::CountNonManifoldEdgeFF(original) > 0) { std::cout << "Input mesh has non manifold edges" << std::endl; return nullptr; } // for closed watertight mesh try to split if (Clean::CountHoles(original) < 1) { CreaseCut(original, vcg::math::ToRad(70.0)); Allocator::CompactEveryVector(original); UpdateTopology::FaceFace(original); UpdateFlags::FaceBorderFromFF(original); UpdateFlags::VertexBorderFromFaceAdj(original); #ifdef DEBUG_VORO io::Exporter::Save(original, "creaseSplit.ply", 0); #endif } // One CC std::vector ccs = splitCC(original); if (ccs.empty()) return RemeshOneCC(original, samplingRadius, borderCreaseAngleThreshold); // Multiple CCs std::cout << "Remeshing " << ccs.size() << " components" << std::endl; for (size_t i=0; i(); for (MeshPtr & mesh : ccs) { Append::Mesh(*ret, *mesh); } Clean::RemoveDuplicateVertex(*ret, true); return ret; } /// /// \brief RemeshOneCC the function that remeshes a single connected component mesh preserving its boundary (consistently for eventually adjacent meshes). /// \param original the mesh /// \param samplingRadius is the sampling ragius for remeshing /// \param borderCreaseAngleThreshold is the angle treshold for preserving corner points on the mesh boundary /// \return the remeshed mesh /// static inline MeshPtr RemeshOneCC(Mesh & original, const ScalarType samplingRadius, const ScalarType borderCreaseAngleThreshold = 0.0, int idx = 0) { RequireCompactness(original); RequirePerFaceFlags(original); UpdateTopology::FaceFace(original); UpdateFlags::FaceBorderFromFF(original); UpdateFlags::VertexBorderFromFaceAdj(original); // Resample border Mesh poissonEdgeMesh; { typedef typename EdgeMeshType::CoordType Coord; EdgeMeshType em; // ThisType::ExtractMeshSides(original, em); ThisType::ExtractMeshBorders(original, em); // wtf we should close the loops Clean::RemoveDuplicateVertex(em); Allocator::CompactVertexVector(em); Allocator::CompactEdgeVector(em); #ifdef DEBUG_VORO io::ExporterOBJ::Save(em, QString("edgeMesh_%1.obj").arg(idx).toStdString().c_str(), io::Mask::IOM_EDGEINDEX); #endif // eventually split on 'creases' if (borderCreaseAngleThreshold > 0.0) { UpdateFlags::VertexClearS(em); UpdateFlags::VertexClearV(em); Clean::SelectCreaseVertexOnEdgeMesh(em, vcg::math::ToRad(borderCreaseAngleThreshold)); std::cout << Clean::SplitSelectedVertexOnEdgeMesh(em) << " splits" << std::endl; } #ifdef DEBUG_VORO io::ExporterOBJ::Save(em, QString("edgeMesh_split_%1.obj").arg(idx).toStdString().c_str(), io::Mask::IOM_EDGEINDEX); #endif // Samples vector std::vector borderSamples; TrivialSampler ps(borderSamples); // uniform sampling UpdateTopology::EdgeEdge(em); SurfaceSampling::EdgeMeshUniform(em, ps, samplingRadius, true); BuildMeshFromCoordVector(poissonEdgeMesh, borderSamples); UpdateBounding::Box(poissonEdgeMesh); // remove duplicate vertices Clean::RemoveDuplicateVertex(poissonEdgeMesh, false); Allocator::CompactVertexVector(poissonEdgeMesh); // select all vertices (to mark them fixed) UpdateFlags::VertexSetS(poissonEdgeMesh); #ifdef DEBUG_VORO // // temp remove // UpdateColor::PerVertexConstant(poissonEdgeMesh, vcg::Color4b::Gray); // typedef typename vcg::SpatialHashTable HashVertexGrid; // HashVertexGrid HG; // HG.Set(poissonEdgeMesh.vert.begin(),poissonEdgeMesh.vert.end()); // for (size_t i=0; i(poissonEdgeMesh, HG, creases[i], dist_upper_bound, dist); // assert(vp); // vp->C() = vcg::Color4b::Red; // } io::ExporterPLY::Save(poissonEdgeMesh, QString("borderMesh_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); #endif } typedef VoronoiProcessing Voronoi; typedef TrivialSampler BaseSampler; typedef SurfaceSampling SurfaceSampler; typedef SurfaceSampling SurfaceFixSampler; // copy original mesh Mesh baseMesh; Append::MeshCopy(baseMesh, original, false, true); // refine to obtain a base mesh VoronoiProcessingParameter vpp; vpp.refinementRatio = 4.0f; Voronoi::PreprocessForVoronoi(baseMesh, samplingRadius, vpp); // Poisson sampling preserving border Mesh poissonMesh; std::vector seedPointVec; std::vector seedFixedVec; FixSampler fix_sampler(seedPointVec, seedFixedVec); // montecarlo sampler std::vector sampleVec; BaseSampler mps(sampleVec); // NOTE in order to make results always the same the random sampling generator is reinitialized for // for each patch resampling SurfaceSampler::SamplingRandomGenerator().initialize(5489u); // Montecarlo oversampling Mesh montecarloMesh; int poissonCount = SurfaceSampler::ComputePoissonSampleNum(original, samplingRadius) * 0.7; std::cout << "poisson Count: " << poissonCount << std::endl; if (poissonCount <= 0) { // no need for internal sampling Append::MeshCopy(poissonMesh, poissonEdgeMesh); for (auto vi = poissonEdgeMesh.vert.begin(); vi != poissonEdgeMesh.vert.end(); vi++) { fix_sampler.AddVert(*vi); } } else { // Montecarlo poisson sampling SurfaceSampler::MontecarloPoisson(original, mps, poissonCount * 20); BuildMeshFromCoordVector(montecarloMesh,sampleVec); #ifdef DEBUG_VORO io::ExporterPLY::Save(montecarloMesh, QString("montecarloMesh_%1.ply").arg(idx).toStdString().c_str()); #endif // Poisson disk pruning initialized with edges typename SurfaceFixSampler::PoissonDiskParam pp; pp.preGenMesh = &poissonEdgeMesh; pp.preGenFlag = true; SurfaceFixSampler::PoissonDiskPruning(fix_sampler, montecarloMesh, samplingRadius, pp); #ifdef DEBUG_VORO BuildMeshFromCoordVector(poissonMesh,seedPointVec); io::ExporterPLY::Save(poissonMesh, QString("poissonMesh_%1.ply").arg(idx).toStdString().c_str()); #endif } std::cout << "poisson samples " << seedPointVec.size() << std::endl; // restricted relaxation with fixed points vpp.seedPerturbationProbability = 0.0f; // TODO check preserveFixedSeed flag (NO) Voronoi::RestrictedVoronoiRelaxing(baseMesh, seedPointVec, seedFixedVec, VoroRelaxationStep, vpp); #ifdef DEBUG_VORO BuildMeshFromCoordVector(poissonMesh,seedPointVec); io::ExporterPLY::Save(poissonMesh, QString("relaxedMesh_%1.ply").arg(idx).toStdString().c_str()); #endif // FAIL? MeshPtr finalMeshPtr = std::make_shared(); std::vector seedVertexVec; // Voronoi::SeedToVertexConversion(baseMesh, seedPointVec, seedVertexVec, false); ThisType::SeedToFixedBorderVertexConversion(baseMesh, seedPointVec, seedFixedVec, seedVertexVec); EuclideanDistance dd; std::cout << "BEGIN compute vertex sources (basemesh vn:" << baseMesh.VN() << " fn:" << baseMesh.FN() << ")" << std::endl; Voronoi::ComputePerVertexSources(baseMesh, seedVertexVec, dd); std::cout << "END compute vertex sources" << std::endl; // Voronoi::ConvertDelaunayTriangulationToMesh(baseMesh, *finalMeshPtr, seedVertexVec, false); // traditional ThisType::ConvertDelaunayTriangulationExtendedToMesh(baseMesh, *finalMeshPtr, seedVertexVec); // border-preserving #ifdef DEBUG_VORO io::ExporterPLY::Save(*finalMeshPtr, QString("voroMesh_%1.ply").arg(idx).toStdString().c_str()); io::ExporterPLY::Save(baseMesh, QString("baseMesh_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); #endif return finalMeshPtr; } protected: static inline void ExtractMeshBorders(Mesh & mesh, EdgeMeshType & sides) { RequireFFAdjacency(mesh); RequireVFAdjacency(mesh); // clean the edge mesh containing the borders sides.Clear(); // gather into separate vertices lists std::vector > edges; for (auto fi = mesh.face.begin(); fi != mesh.face.end(); fi++) { for (int e=0; eVN(); e++) { if (vcg::face::IsBorder(*fi, e)) { std::vector tmp; tmp.push_back(fi->V(e)); tmp.push_back(fi->V((e+1)%fi->VN())); edges.push_back(tmp); } } } // convert to edge mesh for (auto & e : edges) { assert(e.size() >= 2); std::vector newVtx; // insert new vertices and store their pointer auto vi = Allocator::AddVertices(sides, e.size()); for (const auto & v : e) { vi->ImportData(*v); newVtx.push_back(&(*vi++)); } auto ei = Allocator::AddEdges(sides, e.size() - 1); for (int i=0; i(e.size() - 1); i++) { ei->V(0) = newVtx[i]; ei->V(1) = newVtx[i+1]; ei++; } } Clean::RemoveDuplicateVertex(sides); } /// /// \brief ExtractMeshSides /// \param mesh the mesh (topology already computed) /// \param sides the edge mesh filled with the extracted borders /// static inline void ExtractMeshSides(Mesh & mesh, EdgeMeshType & sides) { // TODO change this.... maybe wrong RequireFFAdjacency(mesh); RequireVFAdjacency(mesh); // clean the edge mesh containing the borders sides.Clear(); // find a border edge assert(Clean::CountHoles(mesh) >= 1); PosType pos; for (auto fi = mesh.face.begin(); fi != mesh.face.end() && pos.IsNull(); fi++) { for (int e=0; eVN(); e++) { if (vcg::face::IsBorder(*fi, e)) { pos = PosType(&(*fi), e); break; } } } assert(!pos.IsNull()); assert(pos.IsBorder()); // navigate to a corner VertexType * v = pos.V(); do { pos.NextB(); } while(!pos.V()->IsV() && pos.V() != v); // if it's a loop mark the initial point as a corner pos.V()->SetV(); v = pos.V(); // gather into separate vertices lists std::vector > edges; std::vector edgePtrVec; do { edgePtrVec.push_back(pos.V()); pos.NextB(); if (pos.V()->IsV()) { edgePtrVec.push_back(pos.V()); edges.push_back(edgePtrVec); edgePtrVec.clear(); } } while (pos.V() != v); // convert to edge mesh for (auto & e : edges) { assert(e.size() >= 2); std::vector newVtx; // insert new vertices and store their pointer auto vi = Allocator::AddVertices(sides, e.size()); for (const auto & v : e) { vi->ImportData(*v); newVtx.push_back(&(*vi++)); } auto ei = Allocator::AddEdges(sides, e.size() - 1); for (int i=0; i(e.size() - 1); i++) { ei->V(0) = newVtx[i]; ei->V(1) = newVtx[i+1]; ei++; } } } static void SeedToFixedBorderVertexConversion(MeshType & m, const std::vector & seedPVec, const std::vector & seedFixed, std::vector & seedVVec) { // TODO mark here all seeds (cross-border) typedef typename vcg::SpatialHashTable HashVertexGrid; seedVVec.clear(); UpdateTopology::FaceFace(m); UpdateFlags::VertexBorderFromFaceAdj(m); typename MeshType::BoxType bbox = m.bbox; bbox.Offset(bbox.Diag()/4.0); // internal vertices grid HashVertexGrid HG; HG.Set(m.vert.begin(),m.vert.end(), bbox); // boundary vertices grid MeshType borderMesh; HashVertexGrid borderHG; { // get border vertices and build another mesh std::vector borderPts; for (auto vit=m.vert.begin(); vit!=m.vert.end(); vit++) { if (!vit->IsD() && vit->IsB()) borderPts.push_back(vit->cP()); } BuildMeshFromCoordVector(borderMesh,borderPts); borderMesh.bbox = m.bbox; borderHG.Set(borderMesh.vert.begin(), borderMesh.vert.end(), bbox); } const float dist_upper_bound=m.bbox.Diag()/4.0; VertexType * vp = NULL; for( size_t i = 0; i < seedPVec.size(); i++) { const CoordType & p = seedPVec[i]; const bool fixed = seedFixed[i]; if (!fixed) { ScalarType dist; vp = GetClosestVertex(m, HG, p, dist_upper_bound, dist); } else { vp = NULL; ScalarType dist; VertexType * borderVp = GetClosestVertex(borderMesh, borderHG, p, dist_upper_bound, dist); if (borderVp) { vp = GetClosestVertex(m, HG, borderVp->cP(), dist_upper_bound, dist); } } if (vp) { seedVVec.push_back(vp); } } } static void ConvertDelaunayTriangulationExtendedToMesh(MeshType &m, MeshType &outMesh, std::vector &seedVec) { typedef VoronoiProcessing Voronoi; RequirePerVertexAttribute(m ,"sources"); RequireCompactness(m); RequireVFAdjacency(m); auto sources = Allocator::template GetPerVertexAttribute (m,"sources"); outMesh.Clear(); UpdateTopology::FaceFace(m); UpdateFlags::FaceBorderFromFF(m); std::map seedMap; // It says if a given vertex of m is a seed (and its index in seedVec) Voronoi::BuildSeedMap(m, seedVec, seedMap); std::vector innerCornerVec, // Faces adjacent to three different regions borderCornerVec; // Faces that are on the border and adjacent to at least two regions. Voronoi::GetFaceCornerVec(m, sources, innerCornerVec, borderCornerVec); // First add all the needed vertices: seeds and corners for(size_t i=0;i::AddVertex(outMesh, seedVec[i]->P(), vcg::Color4b::White); } // Now just add one face for each inner corner for(size_t i=0; iV(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]]; Allocator::AddFace(outMesh, v0, v1, v2 ); } // Now loop around the borders and find the missing delaunay triangles // select border seed vertices only and pick one UpdateFlags::VertexBorderFromFaceAdj(m); UpdateFlags::VertexClearS(m); UpdateFlags::VertexClearV(m); std::vector borderSeeds; for (auto & s : seedVec) { if (s->IsB()) { s->SetS(); borderSeeds.emplace_back(s); } } for (VertexPointer startBorderVertex : borderSeeds) { if (startBorderVertex->IsV()) { continue; } // unvisited border seed found // put the pos on the border PosType pos(startBorderVertex->VFp(), startBorderVertex->VFi()); do { pos.NextE(); } while (!pos.IsBorder() || (pos.VInd() != pos.E())); // check all border edges between each consecutive border seeds pair do { std::vector edgeVoroVertices(1, sources[pos.V()]); // among all sources found do { pos.NextB(); VertexPointer source = sources[pos.V()]; if (edgeVoroVertices.empty() || edgeVoroVertices.back() != source) { edgeVoroVertices.push_back(source); } } while (!pos.V()->IsS()); pos.V()->SetV(); // assert(edgeVoroVertices.size() >= 2); // add face if 3 different voronoi regions are crossed by the edge if (edgeVoroVertices.size() == 3) { VertexPointer v0 = & outMesh.vert[seedMap[edgeVoroVertices[0]]]; VertexPointer v1 = & outMesh.vert[seedMap[edgeVoroVertices[1]]]; VertexPointer v2 = & outMesh.vert[seedMap[edgeVoroVertices[2]]]; Allocator::AddFace(outMesh, v0,v1,v2); } } while ((pos.V() != startBorderVertex)); } Clean::RemoveUnreferencedVertex(outMesh); Allocator::CompactVertexVector(outMesh); } /// /// \brief The FixSampler class is used with poisson disk pruning to preserve selected vertices and /// keep an auxiliary vector indicating wether the sample is fixed or not /// class FixSampler { public: typedef typename MeshType::CoordType CoordType; typedef typename MeshType::VertexType VertexType; FixSampler(std::vector & samples, std::vector & fixed) : sampleVec(samples) , fixedVec (fixed) { reset(); } void reset() { sampleVec.clear(); fixedVec .clear(); } void AddVert(const VertexType &p) { sampleVec.push_back(p.cP()); fixedVec .push_back(p.IsS()); } private: std::vector & sampleVec; std::vector & fixedVec; }; }; } // end namespace tri } // end namespace vcg #endif // REMESHER_H