Rev 59 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
#include <cfloat>
#include "CGLA/Vec3d.h"
#include <queue>
#include "mesh_optimization.h"
#include "HMesh/FaceCirculator.h"
#include "HMesh/VertexCirculator.h"
using namespace std;
using namespace CGLA;
using namespace HMesh;
namespace HMeshUtil
{
// Small utility functions
namespace
{
class LineSeg
{
const CGLA::Vec3d p0;
const CGLA::Vec3d p1;
const float l;
const CGLA::Vec3d dir;
public:
LineSeg(const CGLA::Vec3d& _p0, const CGLA::Vec3d& _p1):
p0(_p0), p1(_p1), l(length(p1-p0)), dir((p1-p0)/l) {}
bool inseg(const CGLA::Vec3d& p) const
{
double t = dot(dir, p-p0);
if(t<0)
return false;
if(t>l)
return false;
return true;
}
};
Vec3d compute_normal(Vec3d* v)
{
Vec3d norm;
for(int i=0;i<4;++i)
{
norm[0] += (v[i][1]-v[(i+1)%4][1])*(v[i][2]+v[(i+1)%4][2]);
norm[1] += (v[i][2]-v[(i+1)%4][2])*(v[i][0]+v[(i+1)%4][0]);
norm[2] += (v[i][0]-v[(i+1)%4][0])*(v[i][1]+v[(i+1)%4][1]);
}
float l = norm.length();
if(l>0.0f)
norm /= l;
return norm;
}
bool would_flip(HalfEdgeIter h)
{
HalfEdgeIter ho = h->opp;
Vec3d v[4];
v[0]=Vec3d(h->vert->pos);
v[1]=Vec3d(ho->vert->pos);
v[2]=Vec3d(h->next->vert->pos);
v[3]=Vec3d(ho->next->vert->pos);
Vec3d dir(0,0,-1);//compute_normal(v);
Vec3d n1a = cross(v[3]-v[0], v[2]-v[0]);
Vec3d n2a = cross(v[2]-v[1], v[3]-v[1]);
if(dot(normalize(n1a),dir) < 0)
return true;
if(dot(normalize(n2a),dir) < 0)
return true;
return false;
}
}
// Energy functionals
namespace
{
class ValencyEnergy: public EnergyFun
{
public:
double delta_energy(HalfEdgeIter he) const
{
FaceIter f = he->face;
VertexIter v1 = he->opp->vert;
VertexIter v2 = he->vert;
VertexIter vo1 = he->next->vert;
VertexIter vo2 = he->opp->next->vert;
int val1 = valency(v1);
int val2 = valency(v2);
int valo1 = valency(vo1);
int valo2 = valency(vo2);
// The optimal valency is four for a boundary vertex
// and six elsewhere.
int t1 = is_boundary(v1) ? 4 : 6;
int t2 = is_boundary(v2) ? 4 : 6;
int to1 = is_boundary(vo1) ? 4 : 6;
int to2 = is_boundary(vo2) ? 4 : 6;
int before =
sqr(val1-t1)+sqr(val2-t2)+
sqr(valo1-to1)+sqr(valo2-to2);
int after =
sqr(valo1+1-to1)+sqr(val1-1-t1)+
sqr(val2-1-t2)+sqr(valo2+1-to2);
return static_cast<double>(after-before);
}
};
class RandomEnergy: public EnergyFun
{
public:
double delta_energy(HalfEdgeIter he) const
{
return static_cast<double>(rand()/static_cast<float>(RAND_MAX));
}
};
class MinAngleEnergy: public EnergyFun
{
double min_angle(const Vec3d& v0,const Vec3d& v1,const Vec3d& v2) const
{
Vec3d a = normalize(v1-v0);
Vec3d b = normalize(v2-v1);
Vec3d c = normalize(v0-v2);
return min(dot(a,-c), min(dot(b,-a), dot(c,-b)));
}
double thresh;
public:
MinAngleEnergy(double _thresh): thresh(_thresh) {}
double delta_energy(HalfEdgeIter he) const
{
FaceIter f = he->face;
FaceIter f2 = he->opp->face;
double t = dot(normal(f), normal(f2));
if(t> thresh || t < -thresh)
{
Vec3d v0(he->vert->pos);
Vec3d v1(he->next->vert->pos);
Vec3d v2(he->opp->vert->pos);
Vec3d v3(he->opp->next->vert->pos);
double before = min(min_angle(v0,v1,v2), min_angle(v0,v2,v3));
double after = min(min_angle(v0,v1,v3), min_angle(v1,v2,v3));
return -(after-before);
}
return DBL_MAX;
}
};
class DihedralEnergy: public EnergyFun
{
const double gamma;
const bool use_alpha;
double cos_ang(const Vec3d& n1, const Vec3d& n2) const
{
return s_max(-1.0, s_min(1.0, dot(n1, n2)));
}
double edge_alpha_energy(Vec3d v1, Vec3d v2, double ca) const
{
return pow(length(v1-v2)*(acos(ca)), 1.0f/gamma);
}
double edge_c_energy(Vec3d v1, Vec3d v2, double ca) const
{
return pow(length(v1-v2)*(1-ca), 1.0f/gamma);
}
mutable double ab_12;
mutable double ab_a1;
mutable double ab_b1;
mutable double ab_2c;
mutable double ab_2d;
mutable double aa_12;
mutable double aa_b1;
mutable double aa_c1;
mutable double aa_2a;
mutable double aa_2d;
public:
DihedralEnergy(double _gamma = 4.0, bool _use_alpha=false):
gamma(_gamma), use_alpha(_use_alpha) {}
void compute_angles(HalfEdgeIter he) const
{
Vec3d va(he->vert->pos);
Vec3d vb(he->opp->vert->pos);
Vec3d vc(he->next->vert->pos);
Vec3d vd(he->opp->next->vert->pos);
FaceIter fa = he->next->opp->face;
FaceIter fb = he->next->next->opp->face;
FaceIter fc = he->opp->next->opp->face;
FaceIter fd = he->opp->next->next->opp->face;
Vec3d n1 = normalize(cross(vc-va, vb-va));
Vec3d n2 = normalize(cross(vb-va, vd-va));
Vec3d na = fa==NULL_FACE_ITER? Vec3d(0) : Vec3d(normal(fa));
Vec3d nb = fb==NULL_FACE_ITER? Vec3d(0) : Vec3d(normal(fb));
Vec3d nc = fc==NULL_FACE_ITER? Vec3d(0) : Vec3d(normal(fc));
Vec3d nd = fd==NULL_FACE_ITER? Vec3d(0) : Vec3d(normal(fd));
Vec3d fn1 = normalize(cross(vb-vc, vd-vc));
Vec3d fn2 = normalize(cross(vd-vc, va-vc));
ab_12 = cos_ang(n1,n2);
ab_a1 = cos_ang(na,n1);
ab_b1 = cos_ang(nb,n1);
ab_2c = cos_ang(n2,nc);
ab_2d = cos_ang(n2,nd);
aa_12 = cos_ang(fn1,fn2);
aa_b1 = cos_ang(nb,fn1);
aa_c1 = cos_ang(nc, fn1);
aa_2a = cos_ang(fn2, na);
aa_2d = cos_ang(fn2,nd);
}
double energy(HalfEdgeIter he) const
{
double a = cos_ang(Vec3d(normal(he->face)),
Vec3d(normal(he->opp->face)));
Vec3d va(he->vert->pos);
Vec3d vb(he->opp->vert->pos);
if(use_alpha)
return edge_alpha_energy(va,vb,a);
return edge_c_energy(va,vb,a);
}
double delta_energy(HalfEdgeIter he) const
{
compute_angles(he);
Vec3d va(he->vert->pos);
Vec3d vb(he->opp->vert->pos);
Vec3d vc(he->next->vert->pos);
Vec3d vd(he->opp->next->vert->pos);
if(use_alpha)
{
double before =
edge_alpha_energy(va,vb,ab_12)
+edge_alpha_energy(va,vc,ab_a1)
+edge_alpha_energy(vc,vb,ab_b1)
+edge_alpha_energy(vd,vb,ab_2c)
+edge_alpha_energy(vd,va,ab_2d);
double after =
edge_alpha_energy(vd,vc,aa_12)
+edge_alpha_energy(vb,vc,aa_b1)
+edge_alpha_energy(vd,vb,aa_c1)
+edge_alpha_energy(va,vc,aa_2a)
+edge_alpha_energy(vd,va,aa_2d);
return after-before;
}
double before =
edge_c_energy(va,vb,ab_12)
+edge_c_energy(va,vc,ab_a1)
+edge_c_energy(vc,vb,ab_b1)
+edge_c_energy(vd,vb,ab_2c)
+edge_c_energy(vd,va,ab_2d);
double after =
edge_c_energy(vd,vc,aa_12)
+edge_c_energy(vb,vc,aa_b1)
+edge_c_energy(vd,vb,aa_c1)
+edge_c_energy(va,vc,aa_2a)
+edge_c_energy(vd,va,aa_2d);
return after-before;
}
double min_angle(HalfEdgeIter he) const
{
compute_angles(he);
return s_min(s_min(s_min(s_min(aa_12, aa_b1), aa_c1), aa_2a), aa_2d);
}
};
class CurvatureEnergy: public EnergyFun
{
// double abs_mean_curv(const Vec3d& v, const vector<Vec3d>& ring) const
// {
// const int N = ring.size();
// vector<Vec3d> norms(N,Vec3d(0));
// for(int i=0;i<N; ++i)
// {
// const Vec3d& vn = ring[i];
// const Vec3d& vnn = ring[(i+1)%N];
// Vec3d n = cross(vnn-v,vn-v);
// double ln = length(vn);
// if(ln> 1e-20)
// norms[i] = n/ln;
// }
// double H = 0.0f;
// for(int i=0;i<N; ++i)
// {
// const Vec3d& vn = ring[i];
// double e_len = length(v-vn);
// double beta =
// acos(max(-1.0, min(1.0, dot(norms[i], norms[(i+N-1)%N]))));
// H += e_len * beta;
// }
// return H/4.0f;
// }
double abs_mean_curv(const Vec3d& v, const vector<Vec3d>& ring) const
{
const int N = ring.size();
double H = 0;
for(int i=0;i<N;++i)
{
Vec3d vnim1 = ring[(i+N-1)%N] - v;
Vec3d vni = ring[i] - v;
Vec3d vnip1 = ring[(i+1)%N] - v;
Vec3d Nm = normalize(cross(vni, vnim1));
Vec3d Np = normalize(cross(vnip1, vni));
double beta = acos(max(-1.0, min(1.0, dot(Nm, Np))));
H += vni.length() * beta;
}
H /= 4;
return H;
}
public:
double delta_energy(HalfEdgeIter he) const
{
VertexIter va = he->vert;
VertexIter vb = he->opp->vert;
VertexIter vc = he->next->vert;
VertexIter vd = he->opp->next->vert;
Vec3d va_pos(va->pos);
Vec3d vb_pos(vb->pos);
Vec3d vc_pos(vc->pos);
Vec3d vd_pos(vd->pos);
vector<Vec3d> va_ring_bef;
vector<Vec3d> va_ring_aft;
vector<Vec3d> vb_ring_bef;
vector<Vec3d> vb_ring_aft;
vector<Vec3d> vc_ring_bef;
vector<Vec3d> vc_ring_aft;
vector<Vec3d> vd_ring_bef;
vector<Vec3d> vd_ring_aft;
for(VertexCirculator c(va); !c.end(); ++c)
{
va_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() != vb)
va_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
}
for(VertexCirculator c(vb); !c.end(); ++c)
{
vb_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() != va)
vb_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
}
for(VertexCirculator c(vc); !c.end(); ++c)
{
vc_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
vc_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() == va)
vc_ring_aft.push_back(Vec3d(vd->pos));
}
for(VertexCirculator c(vd); !c.end(); ++c)
{
vd_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
vd_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() == vb)
vd_ring_aft.push_back(Vec3d(vc->pos));
}
double before =
abs_mean_curv(va_pos,va_ring_bef) +
abs_mean_curv(vb_pos,vb_ring_bef) +
abs_mean_curv(vc_pos,vc_ring_bef) +
abs_mean_curv(vd_pos,vd_ring_bef);
double after =
abs_mean_curv(va_pos,va_ring_aft) +
abs_mean_curv(vb_pos,vb_ring_aft) +
abs_mean_curv(vc_pos,vc_ring_aft) +
abs_mean_curv(vd_pos,vd_ring_aft);
return after-before;
}
};
class GaussCurvatureEnergy: public EnergyFun
{
double gauss_curv(const Vec3d& v, const vector<Vec3d>& ring) const
{
const int N = ring.size();
double asum=0.0f;
double area_sum=0;
for(int i=0;i<N; ++i)
{
const Vec3d& v1 = ring[i];
const Vec3d& v2 = ring[(i+1)%N];
Vec3d a = v1-v;
Vec3d b = v2-v;
asum += acos(max(-1.0, min(1.0, dot(a,b)/(length(a)*length(b)))));
area_sum += 0.5 * length(cross(a,b));
}
return 3*abs(2 * M_PI - asum)/area_sum;
}
public:
double delta_energy(HalfEdgeIter he) const
{
VertexIter va = he->vert;
VertexIter vb = he->opp->vert;
VertexIter vc = he->next->vert;
VertexIter vd = he->opp->next->vert;
Vec3d va_pos(va->pos);
Vec3d vb_pos(vb->pos);
Vec3d vc_pos(vc->pos);
Vec3d vd_pos(vd->pos);
vector<Vec3d> va_ring_bef;
vector<Vec3d> va_ring_aft;
vector<Vec3d> vb_ring_bef;
vector<Vec3d> vb_ring_aft;
vector<Vec3d> vc_ring_bef;
vector<Vec3d> vc_ring_aft;
vector<Vec3d> vd_ring_bef;
vector<Vec3d> vd_ring_aft;
for(VertexCirculator c(va); !c.end(); ++c)
{
va_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() != vb)
va_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
}
for(VertexCirculator c(vb); !c.end(); ++c)
{
vb_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() != va)
vb_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
}
for(VertexCirculator c(vc); !c.end(); ++c)
{
vc_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
vc_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() == va)
vc_ring_aft.push_back(Vec3d(vd->pos));
}
for(VertexCirculator c(vd); !c.end(); ++c)
{
vd_ring_bef.push_back(Vec3d(c.get_vertex()->pos));
vd_ring_aft.push_back(Vec3d(c.get_vertex()->pos));
if(c.get_vertex() == vb)
vd_ring_aft.push_back(Vec3d(vc->pos));
}
double before =
gauss_curv(va_pos,va_ring_bef) +
gauss_curv(vb_pos,vb_ring_bef) +
gauss_curv(vc_pos,vc_ring_bef) +
gauss_curv(vd_pos,vd_ring_bef);
double after =
gauss_curv(va_pos,va_ring_aft) +
gauss_curv(vb_pos,vb_ring_aft) +
gauss_curv(vc_pos,vc_ring_aft) +
gauss_curv(vd_pos,vd_ring_aft);
return after-before;
}
};
struct PQElem
{
double pri;
HalfEdgeIter he;
int time;
PQElem() {}
PQElem(double _pri, HalfEdgeIter _he, int _time):
pri(_pri), he(_he), time(_time) {}
};
bool operator<(const PQElem& e0, const PQElem& e1)
{
return e0.pri > e1.pri;
}
void add_to_queue(priority_queue<PQElem>& Q,
HalfEdgeIter he,
const EnergyFun& efun)
{
if(!is_boundary(he))
{
double energy = efun.delta_energy(he);
int t = he->touched + 1;
he->touched = t;
he->opp->touched = t;
if((energy<0) && (t < 10000))
Q.push(PQElem(energy, he, t));
}
}
void add_one_ring_to_queue(priority_queue<PQElem>& Q,
VertexIter vi,
const EnergyFun& efun)
{
for(VertexCirculator vc(vi); !vc.end(); ++vc)
{
add_to_queue(Q, vc.get_halfedge(), efun);
}
}
}
void priority_queue_optimization(Manifold& m, const EnergyFun& efun)
{
priority_queue<PQElem> Q;
{
HalfEdgeIter he = m.halfedges_begin();
for(;he != m.halfedges_end(); ++he)
he->touched = 0;
he = m.halfedges_begin();
for(;he != m.halfedges_end(); ++he)
if(he->touched == 0)
add_to_queue(Q, he, efun);
}
double pri_old = -DBL_MAX;
while(!Q.empty())
{
PQElem elem = Q.top();
Q.pop();
HalfEdgeIter he = elem.he;
if(he->touched == elem.time)
{
if(m.flip(he))
{
add_one_ring_to_queue(Q, he->vert, efun);
add_one_ring_to_queue(Q, he->next->vert, efun);
add_one_ring_to_queue(Q, he->opp->vert, efun);
add_one_ring_to_queue(Q, he->opp->next->vert, efun);
}
}
}
}
void simulated_annealing_optimization(Manifold& m,
const EnergyFun& efun,
int max_iter)
{
srand(0);
int swaps;
int iter = 0;
double T = 1;
double tmin=0;
double tsum=0;
{
HalfEdgeIter he = m.halfedges_begin();
for(;he != m.halfedges_end(); ++he)
{
if(!is_boundary(he))
{
double e = efun.delta_energy(he);
tmin = s_min(e, tmin);
tsum += efun.energy(he);
}
}
}
cout << "Initial energy:" << tsum << endl;
T = -2*tmin;
if(max_iter>0) do
{
vector<HalfEdgeIter> halfedges;
HalfEdgeIter he = m.halfedges_begin();
for(;he != m.halfedges_end(); ++he)
{
if(!is_boundary(he))
halfedges.push_back(he);
}
random_shuffle(halfedges.begin(), halfedges.end());
swaps = 0;
for(int i=0;i<halfedges.size();++i)
{
he = halfedges[i];
// if(!would_flip(he))
DihedralEnergy dih_en;
if(dih_en.min_angle(he) > -0.4)
{
double delta = efun.delta_energy(he);
if(delta < -1e-8)
{
if(m.flip(he))
++swaps;
}
else
{
delta = max(1e-8, delta);
double prob = min(0.9999, exp(-delta/T));
if(rand()/double(RAND_MAX) < prob)
{
if(m.flip(he))
++swaps;
}
}
}
}
cout << "Swaps = " << swaps << " T = " << T << endl;
if(iter % 5 == 0 && iter > 0)
T *= 0.9;
}
while(++iter < max_iter && swaps);
cout << "Iterations " << iter << endl;
}
void minimize_dihedral_angle(Manifold& m,
int iter,
bool anneal,
bool alpha,
double gamma)
{
DihedralEnergy energy_fun(gamma, alpha);
if(anneal)
simulated_annealing_optimization(m, energy_fun, iter);
else
priority_queue_optimization(m, energy_fun);
}
void randomize_mesh(Manifold& m, int max_iter)
{
RandomEnergy energy_fun;
simulated_annealing_optimization(m, energy_fun, max_iter);
}
void minimize_curvature(Manifold& m, bool anneal)
{
CurvatureEnergy energy_fun;
if(anneal)
simulated_annealing_optimization(m, energy_fun);
else
priority_queue_optimization(m, energy_fun);
}
void minimize_gauss_curvature(Manifold& m, bool anneal)
{
GaussCurvatureEnergy energy_fun;
if(anneal)
simulated_annealing_optimization(m, energy_fun);
else
priority_queue_optimization(m, energy_fun);
}
void maximize_min_angle(Manifold& m, float thresh, bool anneal)
{
MinAngleEnergy energy_fun(thresh);
if(anneal)
simulated_annealing_optimization(m, energy_fun);
else
priority_queue_optimization(m, energy_fun);
}
void optimize_valency(Manifold& m, bool anneal)
{
ValencyEnergy energy_fun;
if(anneal)
simulated_annealing_optimization(m, energy_fun);
else
priority_queue_optimization(m, energy_fun);
}
}