vcglib/vcg/complex/algorithms/isotropic_remeshing.h

1271 lines
38 KiB
C++

/****************************************************************************
* 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 _VCG_ISOTROPICREMESHING_H
#define _VCG_ISOTROPICREMESHING_H
#include<vcg/complex/algorithms/update/quality.h>
#include<vcg/complex/algorithms/update/curvature.h>
#include<vcg/complex/algorithms/update/normal.h>
#include<vcg/complex/algorithms/refine.h>
#include<vcg/complex/algorithms/stat.h>
#include<vcg/complex/algorithms/smooth.h>
#include<vcg/complex/algorithms/local_optimization/tri_edge_collapse.h>
#include<vcg/space/index/spatial_hashing.h>
#include <wrap/io_trimesh/export.h>
namespace vcg {
namespace tri {
template<class TRI_MESH_TYPE>
class IsotropicRemeshing
{
public:
typedef TRI_MESH_TYPE MeshType;
typedef typename MeshType::FaceType FaceType;
typedef typename FaceType::VertexType VertexType;
typedef typename FaceType::VertexPointer VertexPointer;
typedef typename VertexType::ScalarType ScalarType;
typedef typename VertexType::CoordType CoordType;
typedef typename face::Pos<FaceType> PosType;
typedef BasicVertexPair<VertexType> VertexPair;
typedef EdgeCollapser<MeshType, VertexPair> Collapser;
typedef GridStaticPtr<FaceType, ScalarType> StaticGrid;
typedef struct Params {
typedef struct Stat {
int splitNum;
int collapseNum;
int flipNum;
void Reset() {
splitNum=0;
collapseNum=0;
flipNum=0;
}
} Stat;
ScalarType minLength; // minimal admitted length: no edge should be shorter than this value (used when collapsing)
ScalarType maxLength; // maximal admitted length: no edge should be longer than this value (used when refining)
ScalarType lengthThr;
ScalarType minimalAdmittedArea;
ScalarType maxSurfDist;
ScalarType aspectRatioThr = 0.05; //min aspect ratio: during relax bad triangles will be relaxed
ScalarType foldAngleCosThr = cos(math::ToRad(140.)); //min angle to be considered folding: during relax folded triangles will be relaxed
ScalarType creaseAngleRadThr = math::ToRad(10.0);
ScalarType creaseAngleCosThr = cos(math::ToRad(10.0)); //min angle to be considered crease: two faces with normals diverging more than thr share a crease edge
bool splitFlag = true;
bool swapFlag = true;
bool collapseFlag = true;
bool smoothFlag=true;
bool projectFlag=true;
bool selectedOnly = false;
bool userSelectedCreases = false;
bool surfDistCheck = true;
bool adapt=false;
int iter=1;
Stat stat;
void SetTargetLen(const ScalarType len)
{
minLength=len*4./5.;
maxLength=len*4./3.;
lengthThr=len*4./3.;
minimalAdmittedArea = (minLength * minLength)/1000.0;
}
void SetFeatureAngleDeg(const ScalarType angle)
{
creaseAngleRadThr = math::ToRad(angle);
creaseAngleCosThr = cos(creaseAngleRadThr);
}
StaticGrid grid;
MeshType* m;
MeshType* mProject;
} Params;
static void debug_crease (MeshType & toRemesh, std::string prepend, int i)
{
ForEachVertex(toRemesh, [] (VertexType & v) {
v.C() = Color4b::Gray;
v.Q() = 0;
});
ForEachFacePos(toRemesh, [&](PosType &p){
if (p.F()->IsFaceEdgeS(p.E()))
{
p.V()->Q() += 1;
p.VFlip()->Q() += 1;
}
});
ForEachVertex(toRemesh, [] (VertexType & v) {
if (v.Q() >= 4)
v.C() = Color4b::Green;
else if (v.Q() >= 2)
v.C() = Color4b::Red;
});
prepend += "_creases" + std::to_string(i) + ".ply";
vcg::tri::io::Exporter<MeshType>::Save(toRemesh, prepend.c_str(), vcg::tri::io::Mask::IOM_ALL);
}
static void Do(MeshType &toRemesh, Params & params, vcg::CallBackPos * cb=0)
{
MeshType toProjectCopy;
tri::UpdateBounding<MeshType>::Box(toRemesh);
tri::UpdateNormal<MeshType>::PerVertexNormalizedPerFaceNormalized(toRemesh);
tri::Append<MeshType,MeshType>::MeshCopy(toProjectCopy, toRemesh);
Do(toRemesh,toProjectCopy,params,cb);
}
static void Do(MeshType &toRemesh, MeshType &toProject, Params & params, vcg::CallBackPos * cb=0)
{
assert(&toRemesh != &toProject);
params.stat.Reset();
tri::UpdateBounding<MeshType>::Box(toRemesh);
{
tri::UpdateBounding<MeshType>::Box(toProject);
tri::UpdateNormal<MeshType>::PerFaceNormalized(toProject);
params.m = &toRemesh;
params.mProject = &toProject;
params.grid.Set(toProject.face.begin(), toProject.face.end());
}
tri::UpdateTopology<MeshType>::FaceFace(toRemesh);
tri::UpdateFlags<MeshType>::VertexBorderFromFaceAdj(toRemesh);
tri::UpdateTopology<MeshType>::VertexFace(toRemesh);
// computeQuality(toRemesh);
// tri::UpdateQuality<MeshType>::VertexSaturate(toRemesh);
tagCreaseEdges(toRemesh, params);
for(int i=0; i < params.iter; ++i)
{
// params.stat.Reset();
if(cb) cb(100*i/params.iter, "Remeshing");
if(params.splitFlag)
SplitLongEdges(toRemesh, params);
#ifdef DEBUG_CREASE
debug_crease(toRemesh, std::string("after_ref"), i);
#endif
if(params.collapseFlag)
{
CollapseShortEdges(toRemesh, params);
CollapseCrosses(toRemesh, params);
}
if(params.swapFlag)
ImproveValence(toRemesh, params);
if(params.smoothFlag)
ImproveByLaplacian(toRemesh, params);
if(params.projectFlag)
ProjectToSurface(toRemesh, params);
}
}
private:
/*
TODO: Add better crease support: detect all creases at starting time, saving it on facedgesel flags
All operations must then preserve the faceedgesel flag accordingly:
Refinement -> Check that refiner propagates faceedgesel [should be doing it]
Collapse -> Implement 1D edge collapse and better check on corners and creases
Swap -> Totally avoid swapping crease edges [ok]
Smooth -> Apply 1D smoothing to crease vertices + check on
(http://www.cs.ubc.ca/labs/imager/tr/2009/eltopo/sisc2009.pdf)
*/
IsotropicRemeshing() {}
// this returns the value of cos(a) where a is the angle between n0 and n1. (scalar prod is cos(a))
static inline ScalarType fastAngle(Point3<ScalarType> n0, Point3<ScalarType> n1)
{
return math::Clamp(n0*n1,(ScalarType)-1.0,(ScalarType)1.0);
}
// compare the value of the scalar prod with the cos of the crease threshold
static inline bool testCreaseEdge(PosType &p, ScalarType creaseCosineThr)
{
ScalarType angle = fastAngle(NormalizedTriangleNormal(*(p.F())), NormalizedTriangleNormal(*(p.FFlip())));
return angle <= creaseCosineThr && angle >= -0.98;
// return (angle <= creaseCosineThr && angle >= -creaseCosineThr);
}
// this stores in minQ the value of the 10th percentile of the VertQuality distribution and in
// maxQ the value of the 90th percentile.
static inline void computeVQualityDistrMinMax(MeshType &m, ScalarType &minQ, ScalarType &maxQ)
{
Distribution<ScalarType> distr;
tri::Stat<MeshType>::ComputePerVertexQualityDistribution(m,distr);
maxQ = distr.Percentile(0.9f);
minQ = distr.Percentile(0.1f);
}
//Computes PerVertexQuality as a function of the 'deviation' of the normals taken from
//the faces incident to each vertex
static void computeQuality(MeshType &m)
{
tri::RequirePerVertexQuality(m);
tri::UpdateFlags<MeshType>::VertexClearV(m);
for(auto vi=m.vert.begin(); vi!=m.vert.end(); ++vi)
if(!(*vi).IsD())
{
vector<FaceType*> ff;
face::VFExtendedStarVF(&*vi, 0, ff);
ScalarType tot = 0.f;
auto it = ff.begin();
Point3<ScalarType> fNormal = NormalizedTriangleNormal(**it);
++it;
while(it != ff.end())
{
tot+= 1-math::Abs(fastAngle(fNormal, NormalizedTriangleNormal(**it)));
++it;
}
vi->Q() = tot / (ScalarType)(std::max(1, ((int)ff.size()-1)));
vi->SetV();
}
}
/*
Computes the ideal valence for the vertex in pos p:
4 for border vertices
6 for internal vertices
*/
static inline int idealValence(PosType &p)
{
if(p.IsBorder()) return 4;
return 6;
}
static inline int idealValence(VertexType &v)
{
if(v.IsB()) return 4;
return 6;
}
static inline int idealValenceSlow(PosType &p)
{
std::vector<PosType> posVec;
VFOrderedStarFF(p,posVec);
float angleSumRad =0;
for(PosType &ip : posVec)
{
angleSumRad += ip.AngleRad();
}
return (int)(std::ceil(angleSumRad / (M_PI/3.0f)));
}
static bool testHausdorff (MeshType & m, StaticGrid & grid, const std::vector<CoordType> & verts, const ScalarType maxD)
{
for (CoordType v : verts)
{
CoordType closest;
ScalarType dist = 0;
FaceType* fp = GetClosestFaceBase(m, grid, v, maxD, dist, closest);
if (fp == NULL)
{
return false;
}
}
return true;
}
static int tagCreaseEdges(MeshType &m, Params & params)
{
int count = 0;
std::vector<char> creaseVerts(m.VN(), 0);
vcg::tri::UpdateFlags<MeshType>::VertexClearV(m);
std::queue<PosType> creaseQueue;
ForEachFacePos(m, [&](PosType &p){
if (p.IsBorder())
p.F()->SetFaceEdgeS(p.E());
// if((p.FFlip() > p.F()))
{
FaceType *ff = p.F();
FaceType *ffAdj = p.FFlip();
double quality = vcg::QualityRadii(ff->cP(0), ff->cP(1), ff->cP(2));
double qualityAdj = vcg::QualityRadii(ffAdj->cP(0), ffAdj->cP(1), ffAdj->cP(2));
bool qualityCheck = quality > 0.00000001 && qualityAdj > 0.00000001;
// bool areaCheck = vcg::DoubleArea(*ff) > 0.000001 && vcg::DoubleArea(*ffAdj) > 0.000001;
if (!params.userSelectedCreases && (testCreaseEdge(p, params.creaseAngleCosThr) /*&& areaCheck*//* && qualityCheck*/) || p.IsBorder())
{
PosType pp = p;
do {
pp.F()->SetFaceEdgeS(pp.E());
pp.NextF();
} while (pp != p);
creaseQueue.push(p);
}
}
});
// //now all creases are checked...
// //prune false positive (too small) (count + scale?)
// while (!creaseQueue.empty())
// {
// PosType & p = creaseQueue.front();
// creaseQueue.pop();
// std::stack<PosType> chainQueue;
// std::vector<size_t> chainVerts;
// if (!p.V()->IsV())
// {
// chainQueue.push(p);
// }
// p.FlipV();
// p.NextEdgeS();
// if (!p.V()->IsV())
// {
// chainQueue.push(p);
// }
// while (!chainQueue.empty())
// {
// PosType p = chainQueue.top();
// chainQueue.pop();
// p.V()->SetV();
// chainVerts.push_back(vcg::tri::Index(m, p.V()));
// PosType pp = p;
// //circle around vert in search for new crease edges
// do {
// pp.NextF(); //jump adj face
// pp.FlipE(); // this edge is already ok => jump to next
// if (pp.IsEdgeS())
// {
// PosType nextPos = pp;
// nextPos.FlipV(); // go to next vert in the chain
// if (!nextPos.V()->IsV()) // if already visited...ignore
// {
// chainQueue.push(nextPos);
// }
// }
// }
// while (pp != p);
// }
// if (chainVerts.size() > 5)
// {
// for (auto vp : chainVerts)
// {
// creaseVerts[vp] = 1;
// }
// }
// }
// //store crease on V()
// //this aspect ratio check doesn't work on cadish meshes (long thin triangles spanning whole mesh)
// ForEachFace(m, [&] (FaceType & f) {
// if (vcg::QualityRadii(f.cP(0), f.cP(1), f.cP(2)) < params.aspectRatioThr)
// {
// if (creaseVerts[vcg::tri::Index(m, f.V(0))] == 0)
// f.V(0)->SetS();
// if (creaseVerts[vcg::tri::Index(m, f.V(1))] == 0)
// f.V(1)->SetS();
// if (creaseVerts[vcg::tri::Index(m, f.V(2))] == 0)
// f.V(2)->SetS();
// }
// });
// ForEachFace(m, [&] (FaceType & f) {
// for (int i = 0; i < 3; ++i)
// {
// if (f.FFp(i) > &f)
// {
// ScalarType angle = fastAngle(NormalizedTriangleNormal(f), NormalizedTriangleNormal(*(f.FFp(i))));
// if (angle <= params.foldAngleCosThr)
// {
// // if (creaseVerts[vcg::tri::Index(m, f.V0(i))] == 0)
// f.V0(i)->SetS();
// // if (creaseVerts[vcg::tri::Index(m, f.V1(i))] == 0)
// f.V1(i)->SetS();
// // if (creaseVerts[vcg::tri::Index(m, f.V2(i))] == 0)
// f.V2(i)->SetS();
// // if (creaseVerts[vcg::tri::Index(m, f.FFp(i)->V2(f.FFi(i)))] == 0)
// f.FFp(i)->V2(f.FFi(i))->SetS();
// }
// }
// }
// });
return count;
}
/*
Edge Swap Step:
This method optimizes the valence of each vertex.
oldDist is the sum of the absolute distance of each vertex from its ideal valence
newDist is the sum of the absolute distance of each vertex from its ideal valence after
the edge swap.
If the swap decreases the total absolute distance, then it's applied, preserving the triangle
quality. +1
v1 v1
/ \ /|\
/ \ / | \
/ \ / | \
/ _*p\ -1/ | \ -1
v2--------v0 ========> v2 | v0
\ / \ | /
\ / \ | /
\ / \ | /
\ / \|/ +1
v3 v3
Before Swap After Swap
*/
static bool testSwap(PosType p, ScalarType creaseAngleCosThr)
{
//if border or feature, do not swap
if (/*p.IsBorder() || */p.IsEdgeS()) return false;
int oldDist = 0, newDist = 0, idealV, actualV;
PosType tp=p;
VertexType *v0=tp.V();
std::vector<VertexType*> incident;
vcg::face::VVStarVF<FaceType>(tp.V(), incident);
idealV = idealValence(tp); actualV = incident.size();
oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV - 1));
tp.NextF();tp.FlipE();tp.FlipV();
VertexType *v1=tp.V();
vcg::face::VVStarVF<FaceType>(tp.V(), incident);
idealV = idealValence(tp); actualV = incident.size();
oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV + 1));
tp.FlipE();tp.FlipV();tp.FlipE();
VertexType *v2=tp.V();
vcg::face::VVStarVF<FaceType>(tp.V(), incident);
idealV = idealValence(tp); actualV = incident.size();
oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV - 1));
tp.NextF();tp.FlipE();tp.FlipV();
VertexType *v3=tp.V();
vcg::face::VVStarVF<FaceType>(tp.V(), incident);
idealV = idealValence(tp); actualV = incident.size();
oldDist += abs(idealV - actualV); newDist += abs(idealV - (actualV + 1));
ScalarType qOld = std::min(Quality(v0->P(),v2->P(),v3->P()),Quality(v0->P(),v1->P(),v2->P()));
ScalarType qNew = std::min(Quality(v0->P(),v1->P(),v3->P()),Quality(v2->P(),v3->P(),v1->P()));
return (newDist < oldDist && qNew >= qOld * 0.50f) ||
(newDist == oldDist && qNew > qOld * 1.f) || qNew > 1.5f * qOld;
}
static bool checkManifoldness(FaceType & f, int z)
{
PosType pos(&f, (z+2)%3, f.V2(z));
PosType start = pos;
do {
pos.FlipE();
if (!face::IsManifold(*pos.F(), pos.E()))
break;
pos.FlipF();
} while (pos!=start);
return pos == start;
}
// Edge swap step: edges are flipped in order to optimize valence and triangle quality across the mesh
static void ImproveValence(MeshType &m, Params &params)
{
static ScalarType foldCheckRad = math::ToRad(5.);
tri::UpdateTopology<MeshType>::FaceFace(m);
tri::UpdateTopology<MeshType>::VertexFace(m);
ForEachFace(m, [&] (FaceType & f) {
// if (face::IsManifold(f, 0) && face::IsManifold(f, 1) && face::IsManifold(f, 2))
for (int i = 0; i < 3; ++i)
{
if (&f > f.cFFp(i))
{
PosType pi(&f, i);
CoordType swapEdgeMidPoint = (f.cP2(i) + f.cFFp(i)->cP2(f.cFFi(i))) / 2.;
std::vector<CoordType> toCheck(1, swapEdgeMidPoint);
if(((!params.selectedOnly) || (f.IsS() && f.cFFp(i)->IsS())) &&
!face::IsBorder(f, i) &&
face::IsManifold(f, i) && /*checkManifoldness(f, i) &&*/
face::checkFlipEdgeNotManifold(f, i) &&
testSwap(pi, params.creaseAngleCosThr) &&
(!params.surfDistCheck || testHausdorff(*params.mProject, params.grid, toCheck, params.maxSurfDist)) &&
face::CheckFlipEdgeNormal(f, i, vcg::math::ToRad(5.)))
{
//When doing the swap we need to preserve and update the crease info accordingly
FaceType* g = f.cFFp(i);
int w = f.FFi(i);
bool creaseF = g->IsFaceEdgeS((w + 1) % 3);
bool creaseG = f.IsFaceEdgeS((i + 1) % 3);
face::FlipEdgeNotManifold(f, i);
f.ClearFaceEdgeS((i + 1) % 3);
g->ClearFaceEdgeS((w + 1) % 3);
if (creaseF)
f.SetFaceEdgeS(i);
if (creaseG)
g->SetFaceEdgeS(w);
++params.stat.flipNum;
break;
}
}
}
});
}
// The predicate that defines which edges should be split
class EdgeSplitAdaptPred
{
public:
int count = 0;
ScalarType length, lengthThr, minQ, maxQ;
bool operator()(PosType &ep)
{
ScalarType mult = math::ClampedLerp((ScalarType)0.5,(ScalarType)1.5, (((math::Abs(ep.V()->Q())+math::Abs(ep.VFlip()->Q()))/(ScalarType)2.0)/(maxQ-minQ)));
ScalarType dist = Distance(ep.V()->P(), ep.VFlip()->P());
if(dist > std::max(mult*length,lengthThr*2))
{
++count;
return true;
}
else
return false;
}
};
class EdgeSplitLenPred
{
public:
int count = 0;
ScalarType squaredlengthThr;
bool operator()(PosType &ep)
{
if(SquaredDistance(ep.V()->P(), ep.VFlip()->P()) > squaredlengthThr)
{
++count;
return true;
}
else
return false;
}
};
//Split pass: This pass uses the tri::RefineE from the vcglib to implement
//the refinement step, using EdgeSplitPred as a predicate to decide whether to split or not
static void SplitLongEdges(MeshType &m, Params &params)
{
tri::UpdateTopology<MeshType>::FaceFace(m);
tri::MidPoint<MeshType> midFunctor(&m);
ScalarType minQ,maxQ;
if(params.adapt){
computeVQualityDistrMinMax(m, minQ, maxQ);
EdgeSplitAdaptPred ep;
ep.minQ = minQ;
ep.maxQ = maxQ;
ep.length = params.maxLength;
ep.lengthThr = params.lengthThr;
tri::RefineE(m,midFunctor,ep);
params.stat.splitNum+=ep.count;
}
else {
EdgeSplitLenPred ep;
ep.squaredlengthThr = params.maxLength*params.maxLength;
tri::RefineMidpoint(m, ep, params.selectedOnly);
params.stat.splitNum+=ep.count;
}
}
static int VtoE(const int v0, const int v1)
{
static /*constexpr*/ int Vmat[3][3] = { -1, 0, 2,
0, -1, 1,
2, 1, -1};
return Vmat[v0][v1];
}
static bool checkCanMoveOnCollapse(PosType p, std::vector<FaceType*> & faces, std::vector<int> & vIdxes, Params &params)
{
bool allIncidentFaceSelected = true;
PosType pi = p;
CoordType dEdgeVector = (p.V()->cP() - p.VFlip()->cP()).Normalize();
int incidentFeatures = 0;
for (size_t i = 0; i < faces.size(); ++i)
// if (faces[i] != p.F() && faces[i] != p.FFlip())
{
if (faces[i]->IsFaceEdgeS(VtoE(vIdxes[i], (vIdxes[i]+1)%3)))
{
incidentFeatures++;
CoordType movingEdgeVector0 = (faces[i]->cP1(vIdxes[i]) - faces[i]->cP(vIdxes[i])).Normalize();
if (std::fabs(movingEdgeVector0 * dEdgeVector) < .9f || !p.IsEdgeS())
return false;
}
if (faces[i]->IsFaceEdgeS(VtoE(vIdxes[i], (vIdxes[i]+2)%3)))
{
incidentFeatures++;
CoordType movingEdgeVector1 = (faces[i]->cP2(vIdxes[i]) - faces[i]->cP(vIdxes[i])).Normalize();
if (std::fabs(movingEdgeVector1 * dEdgeVector) < .9f || !p.IsEdgeS())
return false;
}
allIncidentFaceSelected &= faces[i]->IsS();
}
if (incidentFeatures > 4)
return false;
return params.selectedOnly ? allIncidentFaceSelected : true;
}
static bool checkFacesAfterCollapse (std::vector<FaceType*> & faces, PosType p, const Point3<ScalarType> &mp, Params &params, bool relaxed)
{
for (FaceType* f : faces)
{
if(!(*f).IsD() && f != p.F()) //i'm not a deleted face
{
PosType pi(f, p.V()); //same vertex
VertexType *v0 = pi.V();
VertexType *v1 = pi.F()->V1(pi.VInd());
VertexType *v2 = pi.F()->V2(pi.VInd());
if( v1 == p.VFlip() || v2 == p.VFlip()) //i'm the other deleted face
continue;
//check on new face quality
{
ScalarType newQ = Quality(mp, v1->P(), v2->P());
ScalarType oldQ = Quality(v0->P(), v1->P(), v2->P());
if( newQ <= 0.5*oldQ )
return false;
}
// we prevent collapse that makes edges too long (except for cross)
if(!relaxed)
if((Distance(mp, v1->P()) > params.maxLength || Distance(mp, v2->P()) > params.maxLength))
return false;
Point3<ScalarType> oldN = NormalizedTriangleNormal(*(pi.F()));
Point3<ScalarType> newN = Normal(mp, v1->P(), v2->P()).Normalize();
float div = fastAngle(oldN, newN);
if(div < .0f ) return false;
// // check on new face distance from original mesh
if (params.surfDistCheck)
{
std::vector<CoordType> points(4);
points[0] = (v1->cP() + v2->cP() + mp) / 3.;
points[1] = (v1->cP() + mp) / 2.;
points[2] = (v2->cP() + mp) / 2.;
points[3] = mp;
if (!testHausdorff(*(params.mProject), params.grid, points, params.maxSurfDist))
return false;
}
}
}
return true;
}
//TODO: Refactor code and implement the correct set up of crease info when collapsing towards a crease edge
static bool checkCollapseFacesAroundVert1(PosType &p, Point3<ScalarType> &mp, Params &params, bool relaxed)
{
PosType p0 = p, p1 = p;
p1.FlipV();
vector<int> vi0, vi1;
vector<FaceType*> ff0, ff1;
face::VFStarVF<FaceType>(p0.V(), ff0, vi0);
face::VFStarVF<FaceType>(p1.V(), ff1, vi1);
//check crease-moveability
bool moveable0 = checkCanMoveOnCollapse(p0, ff0, vi0, params) && !p0.V()->IsS();
bool moveable1 = checkCanMoveOnCollapse(p1, ff1, vi1, params) && !p1.V()->IsS();
//if both moveable => go to midpoint
// else collapse on movable one
if (!moveable0 && !moveable1)
return false;
//casting int(true) is always 1 and int(false) = =0
assert(int(true) == 1);
assert(int(false) == 0);
mp = (p0.V()->cP() * int(moveable1) + p1.V()->cP() * int(moveable0)) / (int(moveable0) + int(moveable1));
if (!moveable0)
p = p0;
else
p = p1;
if (checkFacesAfterCollapse(ff0, p0, mp, params, relaxed))
return checkFacesAfterCollapse(ff1, p1, mp, params, relaxed);
return false;
}
static bool testCollapse1(PosType &p, Point3<ScalarType> &mp, ScalarType minQ, ScalarType maxQ, Params &params, bool relaxed = false)
{
ScalarType mult = (params.adapt) ? math::ClampedLerp((ScalarType)0.5,(ScalarType)1.5, (((math::Abs(p.V()->Q())+math::Abs(p.VFlip()->Q()))/(ScalarType)2.0)/(maxQ-minQ))) : (ScalarType)1;
ScalarType dist = Distance(p.V()->P(), p.VFlip()->P());
ScalarType thr = mult*params.minLength;
ScalarType area = DoubleArea(*(p.F()))/2.f;
if(relaxed || (dist < thr || area < params.minLength*params.minLength/100.f))//if to collapse
{
return checkCollapseFacesAroundVert1(p, mp, params, relaxed);
}
return false;
}
//This function is especially useful to enforce feature preservation during collapses
//of boundary edges in planar or near planar section of the mesh
static bool chooseBoundaryCollapse(PosType &p, VertexPair &pair)
{
Point3<ScalarType> collapseNV, collapsedNV0, collapsedNV1;
collapseNV = (p.V()->P() - p.VFlip()->P()).normalized();
vector<VertexType*> vv;
face::VVStarVF<FaceType>(p.V(), vv);
for(VertexType *v: vv)
if(!(*v).IsD() && (*v).IsB() && v != p.VFlip()) //ignore non border
collapsedNV0 = ((*v).P() - p.VFlip()->P()).normalized(); //edge vector after collapse
face::VVStarVF<FaceType>(p.VFlip(), vv);
for(VertexType *v: vv)
if(!(*v).IsD() && (*v).IsB() && v != p.V()) //ignore non border
collapsedNV1 = ((*v).P() - p.V()->P()).normalized(); //edge vector after collapse
float cosine = cos(math::ToRad(1.5f));
float angle0 = fabs(fastAngle(collapseNV, collapsedNV0));
float angle1 = fabs(fastAngle(collapseNV, collapsedNV1));
//if on both sides we deviate too much after collapse => don't collapse
if(angle0 <= cosine && angle1 <= cosine)
return false;
//choose the best collapse (the more parallel one to the previous edge..)
pair = (angle0 >= angle1) ? VertexPair(p.V(), p.VFlip()) : VertexPair(p.VFlip(), p.V());
return true;
}
//The actual collapse step: foreach edge it is collapse iff TestCollapse returns true AND
// the linkConditions are preserved
static void CollapseShortEdges(MeshType &m, Params &params)
{
ScalarType minQ, maxQ;
int candidates = 0;
if(params.adapt)
computeVQualityDistrMinMax(m, minQ, maxQ);
tri::UpdateTopology<MeshType>::VertexFace(m);
tri::UpdateFlags<MeshType>::FaceBorderFromVF(m);
tri::UpdateFlags<MeshType>::VertexBorderFromFaceBorder(m);
SelectionStack<MeshType> ss(m);
ss.push();
{
tri::UpdateTopology<MeshType>::FaceFace(m);
Clean<MeshType>::CountNonManifoldVertexFF(m,true);
//FROM NOW ON VSelection is NotManifold
for(auto fi=m.face.begin(); fi!=m.face.end(); ++fi)
if(!(*fi).IsD() && (params.selectedOnly == false || fi->IsS()))
{
for(auto i=0; i<3; ++i)
{
PosType pi(&*fi, i);
++candidates;
VertexPair bp = VertexPair(pi.V(), pi.VFlip());
Point3<ScalarType> mp = (pi.V()->P()+pi.VFlip()->P())/2.f;
if(testCollapse1(pi, mp, minQ, maxQ, params) && Collapser::LinkConditions(bp))
{
//collapsing on pi.V()
bp = VertexPair(pi.VFlip(), pi.V());
Collapser::Do(m, bp, mp, true);
++params.stat.collapseNum;
break;
}
}
}
}
ss.pop();
}
//Here I just need to check the faces of the cross, since the other faces are not
//affected by the collapse of the internal faces of the cross.
static bool testCrossCollapse(PosType &p, std::vector<FaceType*> ff, std::vector<int> vi, Point3<ScalarType> &mp, Params &params)
{
if(!checkFacesAfterCollapse(ff, p, mp, params, true))
return false;
return true;
}
//Choose the best way to collapse a cross based on the (external) cross vertices valence
//and resulting face quality
// +0 -1
// v1 v1 v1
// /| \ /|\ / \
// / | \ / | \ / \
// / | \ / | \ / \
// / *p| \ -1/ | \ -1 +0/ \+0
// v0-------- v2 ========> v0 | v2 OR v0-------v2
// \ | / \ | / \ /
// \ | / \ | / \ /
// \ | / \ | / \ /
// \ | / \|/ +0 \ / -1
// v3 v3 v3
static bool chooseBestCrossCollapse(PosType &p, VertexPair& bp, vector<FaceType*> &ff)
{
vector<VertexType*> vv0, vv1, vv2, vv3;
VertexType *v0, *v1, *v2, *v3;
v0 = p.F()->V1(p.VInd());
v1 = p.F()->V2(p.VInd());
bool crease[4] = {false, false, false, false};
crease[0] = p.F()->IsFaceEdgeS(VtoE(p.VInd(), (p.VInd()+1)%3));
crease[1] = p.F()->IsFaceEdgeS(VtoE(p.VInd(), (p.VInd()+2)%3));
for(FaceType *f: ff)
if(!(*f).IsD() && f != p.F())
{
PosType pi(f, p.V());
VertexType *fv1 = pi.F()->V1(pi.VInd());
VertexType *fv2 = pi.F()->V2(pi.VInd());
if(fv1 == v0 || fv2 == v0)
{
if (fv1 == 0)
{
v3 = fv2;
crease[3] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+2)%3));
}
else
{
v3 = fv1;
crease[3] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+1)%3));
}
// v3 = (fv1 == v0) ? fv2 : fv1;
}
if(fv1 == v1 || fv2 == v1)
{
if (fv1 == v1)
{
v2 = fv2;
crease[2] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+2)%3));
}
else
{
v2 = fv1;
crease[2] = f->IsFaceEdgeS(VtoE(pi.VInd(), (pi.VInd()+1)%3));
}
// v2 = (fv1 == v1) ? fv2 : fv1;
}
}
face::VVStarVF<FaceType>(v0, vv0);
face::VVStarVF<FaceType>(v1, vv1);
face::VVStarVF<FaceType>(v2, vv2);
face::VVStarVF<FaceType>(v3, vv3);
int nv0 = vv0.size(), nv1 = vv1.size();
int nv2 = vv2.size(), nv3 = vv3.size();
int delta1 = (idealValence(*v0) - nv0) + (idealValence(*v2) - nv2);
int delta2 = (idealValence(*v1) - nv1) + (idealValence(*v3) - nv3);
ScalarType Q1 = std::min(Quality(v0->P(), v1->P(), v3->P()), Quality(v1->P(), v2->P(), v3->P()));
ScalarType Q2 = std::min(Quality(v0->P(), v1->P(), v2->P()), Quality(v2->P(), v3->P(), v0->P()));
if (crease[0] || crease[1] || crease[2] || crease[3])
return false;
// if (crease[0] && crease[1] && crease[2] && crease[3])
// {
// return false;
// }
// if (crease[0] || crease[2])
// {
// bp = VertexPair(p.V(), v0);
// return true;
// }
// if (crease[1] || crease[3])
// {
// bp = VertexPair(p.V(), v1);
// return true;
// }
//no crease
if(delta1 < delta2 && Q1 >= 0.6f*Q2)
{
bp = VertexPair(p.V(), v1);
return true;
}
else
{
bp = VertexPair(p.V(), v0);
return true;
}
}
//Cross Collapse pass: This pass cleans the mesh from cross vertices, keeping in mind the link conditions
//and feature preservations tests.
static void CollapseCrosses(MeshType &m , Params &params)
{
tri::UpdateTopology<MeshType>::VertexFace(m);
tri::UpdateFlags<MeshType>::VertexBorderFromNone(m);
int count = 0;
SelectionStack<MeshType> ss(m);
ss.push();
{
tri::UpdateTopology<MeshType>::FaceFace(m);
Clean<MeshType>::CountNonManifoldVertexFF(m,true);
//From now on Selection on vertices is not manifoldness
for(auto fi=m.face.begin(); fi!=m.face.end(); ++fi)
if(!(*fi).IsD() && (!params.selectedOnly || fi->IsS()))
{
for(auto i=0; i<3; ++i)
{
PosType pi(&*fi, i);
if(!pi.V()->IsB())
{
vector<FaceType*> ff;
vector<int> vi;
face::VFStarVF<FaceType>(pi.V(), ff, vi);
//if cross need to check what creases you have and decide where to collapse accordingly
//if tricuspidis need whenever you have at least one crease => can't collapse anywhere
if(ff.size() == 4 || ff.size() == 3)
{
// VertexPair bp;
VertexPair bp = VertexPair(pi.V(), pi.VFlip());
Point3<ScalarType> mp = (pi.V()->P()+pi.VFlip()->P())/2.f;
if(testCollapse1(pi, mp, 0, 0, params, true) && Collapser::LinkConditions(bp))
{
bp = VertexPair(pi.VFlip(), pi.V());
Collapser::Do(m, bp, mp, true);
++count;
break;
}
}
}
}
}
}
ss.pop();
Allocator<MeshType>::CompactEveryVector(m);
}
// This function sets the selection bit on vertices that lie on creases
static int selectVertexFromCrease(MeshType &m, ScalarType creaseThr)
{
int count = 0;
Clean<MeshType>::CountNonManifoldVertexFF(m, true, false);
ForEachFacePos(m, [&](PosType &p){
if(p.IsBorder() || p.IsEdgeS()/*testCreaseEdge(p, creaseThr)*/)
{
p.V()->SetS();
p.VFlip()->SetS();
++count;
}
});
return count;
}
static int selectVertexFromFold(MeshType &m, Params & params)
{
std::vector<char> creaseVerts(m.VN(), 0);
ForEachFacePos(m, [&] (PosType & p) {
if (p.IsEdgeS())
{
creaseVerts[vcg::tri::Index(m, p.V())] = 1;
creaseVerts[vcg::tri::Index(m, p.VFlip())] = 1;
}
});
//this aspect ratio check doesn't work on cadish meshes (long thin triangles spanning whole mesh)
ForEachFace(m, [&] (FaceType & f) {
if (vcg::Quality(f.cP(0), f.cP(1), f.cP(2)) < params.aspectRatioThr || vcg::DoubleArea(f) < 0.00001)
{
if (creaseVerts[vcg::tri::Index(m, f.V(0))] == 0)
f.V(0)->SetS();
if (creaseVerts[vcg::tri::Index(m, f.V(1))] == 0)
f.V(1)->SetS();
if (creaseVerts[vcg::tri::Index(m, f.V(2))] == 0)
f.V(2)->SetS();
}
});
ForEachFace(m, [&] (FaceType & f) {
for (int i = 0; i < 3; ++i)
{
if (f.FFp(i) > &f)
{
ScalarType angle = fastAngle(NormalizedTriangleNormal(f), NormalizedTriangleNormal(*(f.FFp(i))));
if (angle <= params.foldAngleCosThr)
{
if (creaseVerts[vcg::tri::Index(m, f.V0(i))] == 0)
f.V0(i)->SetS();
if (creaseVerts[vcg::tri::Index(m, f.V1(i))] == 0)
f.V1(i)->SetS();
if (creaseVerts[vcg::tri::Index(m, f.V2(i))] == 0)
f.V2(i)->SetS();
if (creaseVerts[vcg::tri::Index(m, f.FFp(i)->V2(f.FFi(i)))] == 0)
f.FFp(i)->V2(f.FFi(i))->SetS();
}
}
}
});
return 0;
}
static void FoldRelax(MeshType &m, Params params, const int step, const bool strict = true)
{
typename vcg::tri::Smooth<MeshType>::LaplacianInfo lpz(CoordType(0, 0, 0), 0);
SimpleTempData<typename MeshType::VertContainer, typename vcg::tri::Smooth<MeshType>::LaplacianInfo> TD(m.vert, lpz);
const ScalarType maxDist = (strict) ? params.maxSurfDist / 1000. : params.maxSurfDist;
for (int i = 0; i < step; ++i)
{
TD.Init(lpz);
vcg::tri::Smooth<MeshType>::AccumulateLaplacianInfo(m, TD, false);
for (auto fi = m.face.begin(); fi != m.face.end(); ++fi)
{
std::vector<CoordType> newPos(4);
bool moving = false;
for (int j = 0; j < 3; ++j)
{
newPos[j] = fi->cP(j);
if (!fi->V(j)->IsD() && TD[fi->V(j)].cnt > 0)
{
if (fi->V(j)->IsS())
{
newPos[j] = (fi->V(j)->P() + TD[fi->V(j)].sum) / (TD[fi->V(j)].cnt + 1);
moving = true;
}
}
}
if (moving)
{
// const CoordType oldN = vcg::NormalizedTriangleNormal(*fi);
// const CoordType newN = vcg::Normal(newPos[0], newPos[1], newPos[2]).Normalize();
newPos[3] = (newPos[0] + newPos[1] + newPos[2]) / 3.;
if (/*(strict || oldN * newN > 0.99) &&*/ (!params.surfDistCheck || testHausdorff(*params.mProject, params.grid, newPos, maxDist)))
{
for (int j = 0; j < 3; ++j)
fi->V(j)->P() = newPos[j];
}
}
}
}
}
static void VertexCoordPlanarLaplacian(MeshType &m, Params & params, int step, ScalarType delta = 0.2)
{
typename vcg::tri::Smooth<MeshType>::LaplacianInfo lpz(CoordType(0, 0, 0), 0);
SimpleTempData<typename MeshType::VertContainer, typename vcg::tri::Smooth<MeshType>::LaplacianInfo> TD(m.vert, lpz);
for (int i = 0; i < step; ++i)
{
TD.Init(lpz);
vcg::tri::Smooth<MeshType>::AccumulateLaplacianInfo(m, TD, false);
// First normalize the AccumulateLaplacianInfo
for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi)
if (!(*vi).IsD() && TD[*vi].cnt > 0)
{
if ((*vi).IsS())
TD[*vi].sum = ((*vi).P() + TD[*vi].sum) / (TD[*vi].cnt + 1);
}
for (auto fi = m.face.begin(); fi != m.face.end(); ++fi)
{
if (!(*fi).IsD())
{
for (int j = 0; j < 3; ++j)
{
if (Angle(Normal(TD[(*fi).V0(j)].sum, (*fi).P1(j), (*fi).P2(j)),
Normal((*fi).P0(j), (*fi).P1(j), (*fi).P2(j))) > M_PI/2.)
TD[(*fi).V0(j)].sum = (*fi).P0(j);
}
}
}
for (auto fi = m.face.begin(); fi != m.face.end(); ++fi)
{
if (!(*fi).IsD())
{
for (int j = 0; j < 3; ++j)
{
if (Angle(Normal(TD[(*fi).V0(j)].sum, TD[(*fi).V1(j)].sum, (*fi).P2(j)),
Normal((*fi).P0(j), (*fi).P1(j), (*fi).P2(j))) > M_PI/2.)
{
TD[(*fi).V0(j)].sum = (*fi).P0(j);
TD[(*fi).V1(j)].sum = (*fi).P1(j);
}
}
}
}
for (auto vi = m.vert.begin(); vi != m.vert.end(); ++vi)
if (!(*vi).IsD() && TD[*vi].cnt > 0)
{
std::vector<CoordType> newPos(1, TD[*vi].sum);
if ((*vi).IsS() && testHausdorff(*params.mProject, params.grid, newPos, params.maxSurfDist))
(*vi).P() = (*vi).P() * (1-delta) + TD[*vi].sum * (delta);
}
} // end step
}
// static int
/**
* Simple Laplacian Smoothing step
* Border and crease vertices are kept fixed.
* If there are selected faces and the param.onlySelected is true we compute
* the set of internal vertices to the selection and we combine it in and with
* the vertexes not on border or creases
*/
static void ImproveByLaplacian(MeshType &m, Params params)
{
SelectionStack<MeshType> ss(m);
if(params.selectedOnly) {
ss.push();
tri::UpdateSelection<MeshType>::VertexFromFaceStrict(m);
ss.push();
}
tri::UpdateTopology<MeshType>::FaceFace(m);
tri::UpdateFlags<MeshType>::VertexBorderFromFaceAdj(m);
tri::UpdateSelection<MeshType>::VertexFromBorderFlag(m);
selectVertexFromCrease(m, params.creaseAngleCosThr);
tri::UpdateSelection<MeshType>::VertexInvert(m);
if(params.selectedOnly) {
ss.popAnd();
}
VertexCoordPlanarLaplacian(m, params, 1);
tri::UpdateSelection<MeshType>::VertexClear(m);
selectVertexFromFold(m, params);
FoldRelax(m, params, 2);
tri::UpdateSelection<MeshType>::VertexClear(m);
if(params.selectedOnly) {
ss.pop();
}
}
/*
Reprojection step, this method reprojects each vertex on the original surface
sampling the nearest Point3 onto it using a uniform grid StaticGrid t
*/
//TODO: improve crease reprojection:
// crease verts should reproject only on creases.
static void ProjectToSurface(MeshType &m, Params & params)
{
for(auto vi=m.vert.begin();vi!=m.vert.end();++vi)
if(!(*vi).IsD())
{
Point3<ScalarType> newP, normP, barP;
ScalarType maxDist = params.maxSurfDist * 1.5f, minDist = 0.f;
FaceType* fp = GetClosestFaceBase(*params.mProject, params.grid, vi->cP(), maxDist, minDist, newP, normP, barP);
if (fp != NULL)
{
vi->P() = newP;
}
}
}
};
} // end namespace tri
} // end namespace vcg
#endif