/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2004-2017 \/)\/ * * 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 //#define DEBUG_VORO 1 //#include #ifdef DEBUG_VORO #include #include #endif namespace vcg { namespace tri { class VoroEdgeMeshAux { class EmEdgeType; class EmVertexType; class EUsedTypes : public vcg::UsedTypes::AsVertexType, vcg::Use::AsEdgeType> {}; class EmVertexType : public vcg::Vertex {}; class EmEdgeType : public vcg::Edge {}; public: class EdgeMeshType : public vcg::tri::TriMesh, std::vector > { public: ~EdgeMeshType() { this->Clear(); this->ClearAttributes(); } }; }; 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; typedef typename VoroEdgeMeshAux::EdgeMeshType EdgeMeshType; /// \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 borderCreaseAngleDeg is the angle treshold for preserving corner points on the mesh boundary /// \param internalCreaseAngleDeg is the angle treshold for preserving creases on the mesh surface (if this value is < 0 it is set to borderCreaseAngleDeg) /// \return the remeshed mesh /// static inline MeshPtr Remesh(Mesh & original, const ScalarType samplingRadius, const ScalarType borderCreaseAngleDeg = 0.0, const ScalarType internalCreaseAngleDeg = -1.0) { RequireFFAdjacency(original); RequireVFAdjacency(original); 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; } const ScalarType borderAngleDeg = std::max(ScalarType(0), borderCreaseAngleDeg); const ScalarType creaseAngleDeg = internalCreaseAngleDeg < 0 ? borderAngleDeg : internalCreaseAngleDeg; // split on creases if (creaseAngleDeg > 0) { CreaseCut(original, vcg::math::ToRad(creaseAngleDeg)); Allocator::CompactEveryVector(original); UpdateTopology::FaceFace(original); UpdateFlags::FaceBorderFromFF(original); UpdateFlags::VertexBorderFromFaceAdj(original); } // Mark the non manifold border vertices as visited on the input mesh // TODO maybe optimize this { // extract border mesh EdgeMeshType em; ThisType::ExtractMeshBorders(original, em); // get the border edge mesh and leave the non manifold vertices only tri::Allocator::CompactEveryVector(em); vcg::tri::Clean::SelectNonManifoldVertexOnEdgeMesh(em); for (EdgeMeshType::VertexType & v : em.vert) { if (!v.IsS()) { tri::Allocator::DeleteVertex(em, v); } } tri::Allocator::CompactVertexVector(em); // clear visited vertices tri::UpdateFlags::VertexClearV(original); if (em.vn != 0) { // iterate over the mesh and mark as visited all the matching vertices with the non manifold border tri::UpdateBounding::Box(em); EdgeMeshType::BoxType bbox = em.bbox; bbox.Offset(bbox.Diag()/1000.0); typedef SpatialHashTable HashVertexGrid; HashVertexGrid HG; HG.Set(em.vert.begin(), em.vert.end(), bbox); typedef EdgeMeshType::CoordType Coord; EdgeMeshType::ScalarType dist_upper_bound = bbox.Diag()/1000.0; for (VertexType & v : original.vert) { EdgeMeshType::ScalarType dist; EdgeMeshType::VertexType * nonManifoldVertex = GetClosestVertex(em, HG, Coord::Construct(v.cP()), dist_upper_bound, dist); if (nonManifoldVertex != NULL && dist == 0) { v.SetV(); } } } } #ifdef DEBUG_VORO io::Exporter::Save(original, "creaseSplit.ply", io::Mask::IOM_VERTCOLOR); #endif // One CC std::vector ccs = splitCC(original); if (ccs.empty()) { return RemeshOneCC(original, samplingRadius, borderAngleDeg); } // 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; } protected: /// /// \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 radius for remeshing /// \param borderCreaseAngleDeg 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 borderCreaseAngleDeg = 0.0, int idx = 0) { // double timeBorders = 0; // double timePoisson = 0; // double timeRelax = 0; // double timeSeed = 0; // double timeSources = 0; // double timeDelaunay = 0; // QElapsedTimer timer; // timer.start(); (void)idx; RequireCompactness(original); RequirePerFaceFlags(original); UpdateTopology::FaceFace(original); UpdateFlags::FaceBorderFromFF(original); UpdateFlags::VertexBorderFromFaceAdj(original); #ifdef DEBUG_VORO io::ExporterPLY::Save(original, QString("cc_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); #endif // Resample border Mesh poissonEdgeMesh; { typedef typename EdgeMeshType::CoordType Coord; EdgeMeshType em; ThisType::ExtractMeshBorders(original, em); Allocator::CompactVertexVector(em); Allocator::CompactEdgeVector(em); // split on non manifold vertices of edgemesh vcg::tri::Clean::SelectNonManifoldVertexOnEdgeMesh(em); { // select also the visited vertices (coming from the non manifold vertices of the whole crease-cut mesh) for (auto & v : em.vert) { if (v.IsV()) { v.SetS(); } } } const int manifoldSplits = vcg::tri::Clean::SplitSelectedVertexOnEdgeMesh(em); (void)manifoldSplits; #ifdef DEBUG_VORO std::cout << manifoldSplits << " non-manifold splits" << std::endl; io::ExporterOBJ::Save(em, QString("edgeMesh_%1.obj").arg(idx).toStdString().c_str(), io::Mask::IOM_EDGEINDEX); #endif // eventually split on 'creases' if (borderCreaseAngleDeg > 0.0) { // split creases UpdateFlags::VertexClearS(em); UpdateFlags::VertexClearV(em); Clean::SelectCreaseVertexOnEdgeMesh(em, vcg::math::ToRad(borderCreaseAngleDeg)); const int splits = Clean::SplitSelectedVertexOnEdgeMesh(em); (void)splits; #ifdef DEBUG_VORO std::cout << splits << " splits" << std::endl; 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 edge sampling UpdateTopology::EdgeEdge(em); SurfaceSampling::EdgeMeshUniform(em, ps, samplingRadius, false); 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 io::ExporterPLY::Save(poissonEdgeMesh, QString("borderMesh_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); #endif } // timeBorders = timer.restart() / 1000.0; 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 std::vector seedPointVec; std::vector seedFixedVec; FixSampler fix_sampler(seedPointVec, seedFixedVec); // montecarlo sampler std::vector sampleVec; BaseSampler mps(sampleVec); // NOTE in order to make the results consistent the random sampling generator is initialized with the same value 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 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 Mesh poissonMesh; BuildMeshFromCoordVector(poissonMesh,seedPointVec); io::ExporterPLY::Save(poissonMesh, QString("poissonMesh_%1.ply").arg(idx).toStdString().c_str()); #endif // timePoisson = timer.restart() / 1000.0; // std::cout << "poisson samples " << seedPointVec.size() << std::endl; // not enough points if (seedPointVec.size() < 3) { return std::make_shared(); } // restricted relaxation with fixed points vpp.seedPerturbationProbability = 0.0f; 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 // timeRelax = timer.restart() / 1000.0; // FAIL? MeshPtr finalMeshPtr = std::make_shared(); std::vector seedVertexVec; // Voronoi::SeedToVertexConversion(baseMesh, seedPointVec, seedVertexVec, false); ThisType::SeedToFixedBorderVertexConversion(baseMesh, samplingRadius, seedPointVec, seedFixedVec, seedVertexVec); EuclideanDistance dd; // timeSeed = timer.restart() / 1000.0; // 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; // timeSources = timer.restart() / 1000.0; // traditional // Voronoi::ConvertDelaunayTriangulationToMesh(baseMesh, *finalMeshPtr, seedVertexVec, false); // border-preserving ThisType::ConvertDelaunayTriangulationExtendedToMesh(baseMesh, *finalMeshPtr, seedVertexVec); #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 // timeDelaunay = timer.elapsed() / 1000.0; // std::cout << "border: " << timeBorders << std::endl // << "poisson: " << timePoisson << std::endl // << "relax: " << timeRelax << std::endl // << "seed: " << timeSeed << std::endl // << "sources: " << timeSources << std::endl // << "delaunay: " << timeDelaunay << std::endl; return finalMeshPtr; } static inline void ExtractMeshBorders(Mesh & mesh, EdgeMeshType & sides) { RequireFFAdjacency(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); } static void SeedToFixedBorderVertexConversion(MeshType & m, const ScalarType samplingRadius, const std::vector & seedPVec, const std::vector & seedFixed, std::vector & seedVVec) { typedef typename vcg::SpatialHashTable HashVertexGrid; seedVVec.clear(); UpdateTopology::FaceFace(m); UpdateFlags::VertexBorderFromFaceAdj(m); typename MeshType::BoxType bbox = m.bbox; bbox.Offset(bbox.Diag()/100.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()); } if (!borderPts.empty()) { BuildMeshFromCoordVector(borderMesh,borderPts); borderMesh.bbox = m.bbox; borderHG.Set(borderMesh.vert.begin(), borderMesh.vert.end(), bbox); } } const ScalarType dist_upper_bound=samplingRadius*4; 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); if (vp) { seedVVec.push_back(vp); } } else { vp = NULL; ScalarType dist; VertexType * borderVp = GetClosestVertex(borderMesh, borderHG, p, dist_upper_bound, dist); if (borderVp) { std::vector dist; std::vector vps; std::vector pts; // vp = GetClosestVertex(m, HG, borderVp->cP(), dist_upper_bound, dist); unsigned int n = GetKClosestVertex(m, HG, 16, borderVp->cP(), dist_upper_bound, vps, dist, pts); if (n>0) { ScalarType d = dist[0]; seedVVec.push_back(vps[0]); assert(dist.size() == size_t(n)); for (size_t j=1; j &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 std::vector > toAdd; 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); if (edgeVoroVertices.size() >= 3) { std::vector v; for (size_t i=0; i3 vertices holes for (size_t i=0; i::AddFace(outMesh, v[0],v[i+1],v[i+2]); } // if (edgeVoroVertices.size() > 3) // { // std::cout << "Weird case: " << edgeVoroVertices.size() << " voroseeds on one border" << std::endl; // } } // // 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); // } // else // { // std::cout << "Weird case!! " << edgeVoroVertices.size() << " voroseeds on one border" << std::endl; // if (edgeVoroVertices.size() == 4) // { // VertexPointer v0 = & outMesh.vert[seedMap[edgeVoroVertices[0]]]; // VertexPointer v1 = & outMesh.vert[seedMap[edgeVoroVertices[1]]]; // VertexPointer v2 = & outMesh.vert[seedMap[edgeVoroVertices[2]]]; // VertexPointer v3 = & outMesh.vert[seedMap[edgeVoroVertices[3]]]; // Allocator::AddFace(outMesh, v0,v1,v2); // Allocator::AddFace(outMesh, v0,v2,v3); // } // } } 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 // _VCGLIB_VORONOI_REMESHER_H