diff --git a/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp b/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp new file mode 100644 index 00000000..523e6389 --- /dev/null +++ b/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +* VCGLib o o * +* Visual and Computer Graphics Library o o * +* _ O _ * +* Copyright(C) 2004-2009 \/)\/ * +* 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. * +* * +****************************************************************************/ +#include +#include +#include +#include + +using namespace vcg; +using namespace std; + +class MyVertex; +class MyFace; +class MyEdge; + +struct MyUsedTypes : public vcg::UsedTypes< + vcg::Use::AsVertexType, + vcg::Use::AsFaceType, + vcg::Use::AsEdgeType> {}; + +class MyVertex: public vcg::Vertex {}; +class MyFace : public vcg::Face {}; +class MyEdge: public vcg::Edge {}; + +class MyMesh : public vcg::tri::TriMesh< std::vector, std::vector > {}; + +int main( int argc, char **argv ) +{ + MyMesh startMesh; + if(argc < 2 ) + { + printf("Usage trimesh_voro mesh region_num\n"); + return -1; + } + printf("Reading %s \n",argv[1]); + int ret= tri::io::Importer::Open(startMesh,argv[1]); + if(ret!=0) + { + printf("Unable to open %s for '%s'\n",argv[1],tri::io::ImporterPLY::ErrorMsg(ret)); + return -1; + } + float samplingRadius = 0.3; + auto remeshed = Remesher::Remesh(startMesh, samplingRadius, 50.0); + + + tri::io::ExporterPLY::Save(*remeshed,"Full.ply",tri::io::Mask::IOM_VERTCOLOR|tri::io::Mask::IOM_WEDGTEXCOORD ); + return 0; +} diff --git a/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.pro b/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.pro new file mode 100644 index 00000000..cba125e8 --- /dev/null +++ b/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.pro @@ -0,0 +1,3 @@ +include(../common.pri) +TARGET = trimesh_voronoi_remesh +SOURCES += trimesh_voronoi_remesh.cpp ../../../wrap/ply/plylib.cpp diff --git a/vcg/complex/algorithms/voronoi_remesher.h b/vcg/complex/algorithms/voronoi_remesher.h new file mode 100644 index 00000000..f586b2a9 --- /dev/null +++ b/vcg/complex/algorithms/voronoi_remesher.h @@ -0,0 +1,705 @@ +/**************************************************************************** +* 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 + +//#define DEBUG_VORO 1 +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; + + ConnectedComponentIterator ci; + for(size_t i=0; i::Clear(mesh); + for(ci.start(mesh, CCV[i].second); !ci.completed(); ++ci) + { + // select all faces for a CC + (*ci)->SetS(); + } + + // create from selected + MeshPtr cc = std::make_shared(); + Append::MeshCopy(*cc, mesh, true); + ret.push_back(cc); + } + + 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) + { + 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 + // (use different sampling radius for the edges) + UpdateTopology::EdgeEdge(em); + SurfaceSampling::EdgeMeshUniform(em, ps, samplingRadius, true); + + // convert to mesh + auto vi = Allocator::AddVertices(poissonEdgeMesh, borderSamples.size()); + for (auto p : borderSamples) + { + vi->P() = CoordType::Construct(p); + vi++; + } + 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) + { + 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