From 75eca5e231319be9d36ac727f59958ae4822ff98 Mon Sep 17 00:00:00 2001 From: Luigi Malomo Date: Mon, 17 Jul 2017 20:39:04 +0200 Subject: [PATCH] fix for voronoi remesher, again --- .../trimesh_voronoi_remesh.cpp | 27 +- vcg/complex/algorithms/voronoi_processing.h | 2 +- vcg/complex/algorithms/voronoi_remesher.h | 1354 ++++++++--------- 3 files changed, 674 insertions(+), 709 deletions(-) diff --git a/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp b/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp index aec60bbd..3280e7a0 100644 --- a/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp +++ b/apps/sample/trimesh_voronoi_remesh/trimesh_voronoi_remesh.cpp @@ -59,17 +59,26 @@ int main( int argc, char **argv ) 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 = startMesh.bbox.Diag() *0.02f; - auto remeshed = Remesher::Remesh(startMesh, samplingRadius, 50.0); + printf("Reading %s \n",argv[1]); + int ret = tri::io::Importer::Open(startMesh,argv[1]); + if(ret!=0) + { + printf("Unable to open %s: '%s'\n",argv[1],tri::io::ImporterPLY::ErrorMsg(ret)); + return -1; + } + tri::UpdateBounding::Box(startMesh); + + float samplingRadius = startMesh.bbox.Diag() * 0.01f; + if (argc == 3) + { + try { + samplingRadius = stof(string(argv[2])); + } catch (exception &) {} + } + std::cout << "using sampling radius: " << samplingRadius << std::endl; + auto remeshed = Remesher::Remesh(startMesh, samplingRadius, 75.0); tri::io::ExporterPLY::Save(*remeshed,"Full.ply",tri::io::Mask::IOM_VERTCOLOR|tri::io::Mask::IOM_WEDGTEXCOORD ); diff --git a/vcg/complex/algorithms/voronoi_processing.h b/vcg/complex/algorithms/voronoi_processing.h index 7b9a6068..c8b72346 100644 --- a/vcg/complex/algorithms/voronoi_processing.h +++ b/vcg/complex/algorithms/voronoi_processing.h @@ -1260,7 +1260,7 @@ static int RestrictedVoronoiRelaxing(MeshType &m, std::vector &seedPo if(sumVec[i].first != 0) { newseedVec.push_back(sumVec[i].second /ScalarType(sumVec[i].first)); - if(vpp.seedPerturbationProbability > RandomGenerator().generate01()) + if(vpp.seedPerturbationProbability > 0 && (vpp.seedPerturbationProbability > RandomGenerator().generate01())) newseedVec.back()+=math::GeneratePointInUnitBallUniform( RandomGenerator())*perturb; newfixedVec.push_back(false); } diff --git a/vcg/complex/algorithms/voronoi_remesher.h b/vcg/complex/algorithms/voronoi_remesher.h index aa6303ce..0effe8df 100644 --- a/vcg/complex/algorithms/voronoi_remesher.h +++ b/vcg/complex/algorithms/voronoi_remesher.h @@ -2,7 +2,7 @@ * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * -* Copyright(C) 2004-2016 \/)\/ * +* Copyright(C) 2004-2017 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * @@ -43,769 +43,725 @@ #include #include -#define DEBUG_VORO 1 + +//#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 {}; + 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(); - } - }; + 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; - + 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 face::Pos PosType; - typedef typename VoroEdgeMeshAux::EdgeMeshType EdgeMeshType; + 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; + } - /// \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 - /// \return the remeshed mesh - /// - static inline MeshPtr Remesh(Mesh & original, const ScalarType samplingRadius, const ScalarType borderCreaseAngleDeg = 0.0) - { - UpdateTopology::FaceFace(original); - UpdateFlags::FaceBorderFromFF(original); - UpdateFlags::VertexBorderFromFaceAdj(original); + static const int VoroRelaxationStep = 20; - RequireFFAdjacency(original); - RequireVFAdjacency(original); - - if (Clean::CountNonManifoldEdgeFF(original) > 0) - { - std::cout << "Input mesh has non manifold edges" << std::endl; - return nullptr; - } - - // split on creases - CreaseCut(original, vcg::math::ToRad(borderCreaseAngleDeg)); - Allocator::CompactEveryVector(original); - UpdateTopology::FaceFace(original); - UpdateFlags::FaceBorderFromFF(original); - UpdateFlags::VertexBorderFromFaceAdj(original); + /// + /// \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 + /// \return the remeshed mesh + /// + static inline MeshPtr Remesh(Mesh & original, const ScalarType samplingRadius, const ScalarType borderCreaseAngleDeg = 0.0) + { + RequireFFAdjacency(original); + RequireVFAdjacency(original); - // Mark the non manifold border vertices as visited on the original mesh -// tri::UpdateColor::PerVertexConstant(original); - { - // extract border mesh - EdgeMeshType em; - ThisType::ExtractMeshBorders(original, em); + UpdateTopology::FaceFace(original); + UpdateFlags::FaceBorderFromFF(original); + UpdateFlags::VertexBorderFromFaceAdj(original); - // 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 (Clean::CountNonManifoldEdgeFF(original) > 0) { - if (!v.IsS()) - { - tri::Allocator::DeleteVertex(em, v); - } + std::cout << "Input mesh has non manifold edges" << std::endl; + return nullptr; } - tri::Allocator::CompactVertexVector(em); + // split on creases + CreaseCut(original, vcg::math::ToRad(borderCreaseAngleDeg)); + Allocator::CompactEveryVector(original); + UpdateTopology::FaceFace(original); + UpdateFlags::FaceBorderFromFF(original); + UpdateFlags::VertexBorderFromFaceAdj(original); - // clear visited vertices - tri::UpdateFlags::VertexClearV(original); - if (em.vn != 0) + // Mark the non manifold border vertices as visited on the input mesh + // TODO maybe optimize this { - // 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()/4.0); - typedef SpatialHashTable HashVertexGrid; - HashVertexGrid HG; - HG.Set(em.vert.begin(), em.vert.end(), bbox); + // extract border mesh + EdgeMeshType em; + ThisType::ExtractMeshBorders(original, em); - typedef EdgeMeshType::CoordType Coord; - EdgeMeshType::ScalarType dist_upper_bound = bbox.Diag()/100.0; - for (VertexType & v : original.vert) + // 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) { - EdgeMeshType::ScalarType dist; - EdgeMeshType::VertexType * nonManifoldVertex = GetClosestVertex(em, HG, Coord::Construct(v.cP()), dist_upper_bound, dist); - if (nonManifoldVertex != NULL && dist == 0) + if (!v.IsS()) { - v.SetV(); -// v.C() = vcg::Color4b::Black; + 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, borderCreaseAngleDeg); } + // Multiple CCs +// std::cout << "Remeshing " << ccs.size() << " components" << std::endl; + for (size_t i=0; i::Save(original, "creaseSplit.ply", io::Mask::IOM_VERTCOLOR); -#endif -// } + MeshPtr ret = std::make_shared(); + 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 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(); + + RequireCompactness(original); + RequirePerFaceFlags(original); + + UpdateTopology::FaceFace(original); + UpdateFlags::FaceBorderFromFF(original); + UpdateFlags::VertexBorderFromFaceAdj(original); - // One CC - std::vector ccs = splitCC(original); - if (ccs.empty()) - return RemeshOneCC(original, samplingRadius, borderCreaseAngleDeg); - - - // 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 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) - { - 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::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(); } - } - } - std::cout << vcg::tri::Clean::SplitSelectedVertexOnEdgeMesh(em) << " non-manifold splits" << std::endl; - #ifdef DEBUG_VORO - io::ExporterOBJ::Save(em, QString("edgeMesh_%1.obj").arg(idx).toStdString().c_str(), io::Mask::IOM_EDGEINDEX); + io::ExporterPLY::Save(original, QString("cc_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); #endif - - // eventually split on 'creases' - if (borderCreaseAngleDeg > 0.0) - { - // split creases - UpdateFlags::VertexClearS(em); - UpdateFlags::VertexClearV(em); - Clean::SelectCreaseVertexOnEdgeMesh(em, vcg::math::ToRad(borderCreaseAngleDeg)); - std::cout << Clean::SplitSelectedVertexOnEdgeMesh(em) << " splits" << std::endl; - } + + // 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); +// std::cout << manifoldSplits << " non-manifold splits" << std::endl; + #ifdef DEBUG_VORO - io::ExporterOBJ::Save(em, QString("edgeMesh_split_%1.obj").arg(idx).toStdString().c_str(), io::Mask::IOM_EDGEINDEX); + io::ExporterOBJ::Save(em, QString("edgeMesh_%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); - + + // 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); +// std::cout << splits << " splits" << std::endl; + } #ifdef DEBUG_VORO - io::ExporterPLY::Save(poissonEdgeMesh, QString("borderMesh_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); + io::ExporterOBJ::Save(em, QString("edgeMesh_split_%1.obj").arg(idx).toStdString().c_str(), io::Mask::IOM_EDGEINDEX); #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); - + + // Samples vector + std::vector borderSamples; + TrivialSampler ps(borderSamples); + + // uniform edge 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 - io::ExporterPLY::Save(montecarloMesh, QString("montecarloMesh_%1.ply").arg(idx).toStdString().c_str()); + io::ExporterPLY::Save(poissonEdgeMesh, QString("borderMesh_%1.ply").arg(idx).toStdString().c_str(), io::Mask::IOM_VERTCOLOR); #endif - - // Poisson disk pruning initialized with edges - typename SurfaceFixSampler::PoissonDiskParam pp; - pp.preGenMesh = &poissonEdgeMesh; - pp.preGenFlag = true; - SurfaceFixSampler::PoissonDiskPruning(fix_sampler, montecarloMesh, samplingRadius, pp); - + } + +// 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 - BuildMeshFromCoordVector(poissonMesh,seedPointVec); - io::ExporterPLY::Save(poissonMesh, QString("poissonMesh_%1.ply").arg(idx).toStdString().c_str()); + io::ExporterPLY::Save(montecarloMesh, QString("montecarloMesh_%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); - + + // 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("relaxedMesh_%1.ply").arg(idx).toStdString().c_str()); + Mesh poissonMesh; + BuildMeshFromCoordVector(poissonMesh,seedPointVec); + io::ExporterPLY::Save(poissonMesh, QString("poissonMesh_%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 - + +// 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 - 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); + BuildMeshFromCoordVector(poissonMesh,seedPointVec); + io::ExporterPLY::Save(poissonMesh, QString("relaxedMesh_%1.ply").arg(idx).toStdString().c_str()); #endif - - return finalMeshPtr; - } - + +// 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; + } + protected: - 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); - } - - - /// - /// \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); - if (vp) + 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++) { - 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) + for (int e=0; eVN(); e++) { - ScalarType d = dist[0]; - seedVVec.push_back(vps[0]); - assert(dist.size() == size_t(n)); - for (size_t j=1; j 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()); + } + 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) { - seedVVec.push_back(vps[j]); - d = dist[j]; - } - else - { - break; + 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 - 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); - // TODO - // 1) handle 5 vertices holes - // 2) make coherent split/border-sampling on different connected components (e.g., left eye raptor50k) + static void ConvertDelaunayTriangulationExtendedToMesh(MeshType &m, + MeshType &outMesh, + std::vector &seedVec) + { + typedef VoronoiProcessing Voronoi; + RequirePerVertexAttribute(m ,"sources"); + RequireCompactness(m); + RequireVFAdjacency(m); - if (edgeVoroVertices.size() >= 3) + 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 v; - 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()) { - v.push_back(&outMesh.vert[seedMap[edgeVoroVertices[i]]]); - } - 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; + s->SetS(); + borderSeeds.emplace_back(s); } } -// // 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; - }; + + 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