Libigl 包含各种各样的几何处理工具和函数,用于处理网格和与之相关的线性代数:本入门教程中讨论的内容太多了。我们在本章中列出了几个有趣的功能来重点介绍。
Irregular vertices:
136/2400 (5.67%)
Areas (Min/Max)/Avg_Area Sigma:
0.01/5.33 (0.87)
Angles in degrees (Min/Max) Sigma:
17.21/171.79 (15.36)
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
MatrixXd V;
MatrixXi F;
igl::readOBJ(TUTORIAL_SHARED_PATH "/horse_quad.obj",V,F);
// Count the number of irregular vertices, the border is ignored
vector<bool> irregular = igl::is_irregular_vertex(V,F);
int vertex_count = V.rows();
int irregular_vertex_count =
std::count(irregular.begin(),irregular.end(),true);
double irregular_ratio = double(irregular_vertex_count)/vertex_count;
printf("Irregular vertices: \n%d/%d (%.2f%%)\n",
irregular_vertex_count,vertex_count, irregular_ratio*100);
// Compute areas, min, max and standard deviation
VectorXd area;
igl::doublearea(V,F,area);
area = area.array() / 2;
double area_avg = area.mean();
double area_min = area.minCoeff() / area_avg;
double area_max = area.maxCoeff() / area_avg;
double area_sigma = sqrt(((area.array()-area_avg)/area_avg).square().mean());
printf("Areas (Min/Max)/Avg_Area Sigma: \n%.2f/%.2f (%.2f)\n",
area_min,area_max,area_sigma);
// Compute per face angles, min, max and standard deviation
MatrixXd angles;
igl::internal_angles(V,F,angles);
angles = 360.0 * (angles/(2*igl::PI)); // Convert to degrees
double angle_avg = angles.mean();
double angle_min = angles.minCoeff();
double angle_max = angles.maxCoeff();
double angle_sigma = sqrt( (angles.array()-angle_avg).square().mean() );
printf("Angles in degrees (Min/Max) Sigma: \n%.2f/%.2f (%.2f)\n",
angle_min,angle_max,angle_sigma);
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Eigen::MatrixXd V,BC;
Eigen::VectorXd W;
Eigen::MatrixXi T,F,G;
double slice_z = 0.5;
enum OverLayType
{
OVERLAY_NONE = 0,
OVERLAY_INPUT = 1,
OVERLAY_OUTPUT = 2,
NUM_OVERLAY = 3,
} overlay = OVERLAY_NONE;
void update_visualization(igl::opengl::glfw::Viewer & viewer)
{
using namespace Eigen;
using namespace std;
Eigen::Vector4d plane(
0,0,1,-((1-slice_z)*V.col(2).minCoeff()+slice_z*V.col(2).maxCoeff()));
MatrixXd V_vis;
MatrixXi F_vis;
VectorXi J;
{
SparseMatrix<double> bary;
// Value of plane's implicit function at all vertices
const VectorXd IV =
(V.col(0)*plane(0) +
V.col(1)*plane(1) +
V.col(2)*plane(2)).array()
+ plane(3);
igl::marching_tets(V,T,IV,V_vis,F_vis,J,bary);
}
VectorXd W_vis;
igl::slice(W,J,W_vis);
MatrixXd C_vis;
// color without normalizing
igl::parula(W_vis,false,C_vis);
const auto & append_mesh = [&C_vis,&F_vis,&V_vis](
const Eigen::MatrixXd & V,
const Eigen::MatrixXi & F,
const RowVector3d & color)
{
F_vis.conservativeResize(F_vis.rows()+F.rows(),3);
F_vis.bottomRows(F.rows()) = F.array()+V_vis.rows();
V_vis.conservativeResize(V_vis.rows()+V.rows(),3);
V_vis.bottomRows(V.rows()) = V;
C_vis.conservativeResize(C_vis.rows()+F.rows(),3);
C_vis.bottomRows(F.rows()).rowwise() = color;
};
switch(overlay)
{
case OVERLAY_INPUT:
append_mesh(V,F,RowVector3d(1.,0.894,0.227));
break;
case OVERLAY_OUTPUT:
append_mesh(V,G,RowVector3d(0.8,0.8,0.8));
break;
default:
break;
}
viewer.data().clear();
viewer.data().set_mesh(V_vis,F_vis);
viewer.data().set_colors(C_vis);
viewer.data().set_face_based(true);
}
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int mod)
{
switch(key)
{
default:
return false;
case ' ':
overlay = (OverLayType)((1+(int)overlay)%NUM_OVERLAY);
break;
case '.':
slice_z = std::min(slice_z+0.01,0.99);
break;
case ',':
slice_z = std::max(slice_z-0.01,0.01);
break;
}
update_visualization(viewer);
return true;
}
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
cout<<"Usage:"<<endl;
cout<<"[space] toggle showing input mesh, output mesh or slice "<<endl;
cout<<" through tet-mesh of convex hull."<<endl;
cout<<"'.'/',' push back/pull forward slicing plane."<<endl;
cout<<endl;
// Load mesh: (V,T) tet-mesh of convex hull, F contains facets of input
// surface mesh _after_ self-intersection resolution
igl::readMESH(TUTORIAL_SHARED_PATH "/big-sigcat.mesh",V,T,F);
// Compute barycenters of all tets
igl::barycenter(V,T,BC);
// Compute generalized winding number at all barycenters
cout<<"Computing winding number over all "<<T.rows()<<" tets..."<<endl;
igl::winding_number(V,F,BC,W);
// Extract interior tets
MatrixXi CT((W.array()>0.5).count(),4);
{
size_t k = 0;
for(size_t t = 0;t<T.rows();t++)
{
if(W(t)>0.5)
{
CT.row(k) = T.row(t);
k++;
}
}
}
// find bounary facets of interior tets
igl::boundary_facets(CT,G);
// boundary_facets seems to be reversed...
G = G.rowwise().reverse().eval();
// normalize
W = (W.array() - W.minCoeff())/(W.maxCoeff()-W.minCoeff());
// Plot the generated mesh
igl::opengl::glfw::Viewer viewer;
update_visualization(viewer);
viewer.callback_key_down = &key_down;
viewer.launch();
}
示例 702 计算具有孔和自相交点(金)的猫内部四面体网格的广义绕组数函数。银网是提取的内部Tet的表面,切片显示凸壳中所有Tet的缠绕数函数:蓝色(~ 0 ),绿色(~ 1),黄色(~2)
igl::decimate(V,F,1000,U,G);
igl::decimate(V,F,shortest_edge_and_midpoint,max_m,U,G);
igl::collapse_edge(0,RowVector3d(0,0,0),V,F,E,EMAP,EF,EI);
igl::collapse_edge(cost_and_placement,V,F,E,EMAP,EF,EI,Q,Qit,C);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
using namespace std;
using namespace Eigen;
using namespace igl;
cout<<"Usage: ./703_Decimation_bin [filename.(off|obj|ply)]"<<endl;
cout<<" [space] toggle animation."<<endl;
cout<<" 'r' reset."<<endl;
// Load a closed manifold mesh
string filename(TUTORIAL_SHARED_PATH "/fertility.off");
if(argc>=2)
{
filename = argv[1];
}
MatrixXd V,OV;
MatrixXi F,OF;
read_triangle_mesh(filename,OV,OF);
igl::opengl::glfw::Viewer viewer;
// Prepare array-based edge data structures and priority queue
VectorXi EMAP;
MatrixXi E,EF,EI;
igl::min_heap< std::tuple<double,int,int> > Q;
Eigen::VectorXi EQ;
// If an edge were collapsed, we'd collapse it to these points:
MatrixXd C;
int num_collapsed;
// Function to reset original mesh and data structures
const auto & reset = [&]()
{
F = OF;
V = OV;
edge_flaps(F,E,EMAP,EF,EI);
C.resize(E.rows(),V.cols());
VectorXd costs(E.rows());
// https://stackoverflow.com/questions/2852140/priority-queue-clear-method
// Q.clear();
Q = {};
EQ = Eigen::VectorXi::Zero(E.rows());
{
Eigen::VectorXd costs(E.rows());
igl::parallel_for(E.rows(),[&](const int e)
{
double cost = e;
RowVectorXd p(1,3);
shortest_edge_and_midpoint(e,V,F,E,EMAP,EF,EI,cost,p);
C.row(e) = p;
costs(e) = cost;
},10000);
for(int e = 0;e<E.rows();e++)
{
Q.emplace(costs(e),e,0);
}
}
num_collapsed = 0;
viewer.data().clear();
viewer.data().set_mesh(V,F);
viewer.data().set_face_based(true);
};
const auto &pre_draw = [&](igl::opengl::glfw::Viewer & viewer)->bool
{
// If animating then collapse 10% of edges
if(viewer.core().is_animating && !Q.empty())
{
bool something_collapsed = false;
// collapse edge
const int max_iter = std::ceil(0.01*Q.size());
for(int j = 0;j<max_iter;j++)
{
if(!collapse_edge(shortest_edge_and_midpoint,V,F,E,EMAP,EF,EI,Q,EQ,C))
{
break;
}
something_collapsed = true;
num_collapsed++;
}
if(something_collapsed)
{
viewer.data().clear();
viewer.data().set_mesh(V,F);
viewer.data().set_face_based(true);
}
}
return false;
};
const auto &key_down =
[&](igl::opengl::glfw::Viewer &viewer,unsigned char key,int mod)->bool
{
switch(key)
{
case ' ':
viewer.core().is_animating ^= 1;
break;
case 'R':
case 'r':
reset();
break;
default:
return false;
}
return true;
};
reset();
viewer.core().background_color.setConstant(1);
viewer.core().is_animating = true;
viewer.callback_key_down = key_down;
viewer.callback_pre_draw = pre_draw;
return viewer.launch();
}
// Initialize AABB tree
igl::AABB<MatrixXd,3> tree;
tree.init(V,T);
VectorXi I;
igl::in_element(V,T,Q,tree,I);
SparseMatrix<int> I;
igl::in_element(V,T,Q,tree,I);
VectorXd sqrD;
VectorXi I;
MatrixXd C;
igl::point_mesh_squared_distance(P,V,F,sqrD,I,C);
igl::AABB tree;
tree.init(V,F);
tree.squared_distance(V,F,P,sqrD,I,C);
... // P changes, but (V,F) does not
tree.squared_distance(V,F,P,sqrD,I,C);
// Choose type of signing to use
igl::SignedDistanceType type = SIGNED_DISTANCE_TYPE_PSEUDONORMAL;
igl::signed_distance(P,V,F,sign_type,S,I,C,N);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Eigen::MatrixXd V;
Eigen::MatrixXi T,F;
igl::AABB<Eigen::MatrixXd,3> tree;
igl::FastWindingNumberBVH fwn_bvh;
Eigen::MatrixXd FN,VN,EN;
Eigen::MatrixXi E;
Eigen::VectorXi EMAP;
double max_distance = 1;
double slice_z = 0.5;
bool overlay = false;
bool useFastWindingNumber = false;
void update_visualization(igl::opengl::glfw::Viewer & viewer)
{
using namespace Eigen;
using namespace std;
Eigen::Vector4d plane(
0,0,1,-((1-slice_z)*V.col(2).minCoeff()+slice_z*V.col(2).maxCoeff()));
MatrixXd V_vis;
MatrixXi F_vis;
// Extract triangle mesh slice through volume mesh and subdivide nasty
// triangles
{
VectorXi J;
SparseMatrix<double> bary;
{
// Value of plane's implicit function at all vertices
const VectorXd IV =
(V.col(0)*plane(0) +
V.col(1)*plane(1) +
V.col(2)*plane(2)).array()
+ plane(3);
igl::marching_tets(V,T,IV,V_vis,F_vis,J,bary);
igl::writeOBJ("vis.obj",V_vis,F_vis);
}
while(true)
{
MatrixXd l;
igl::edge_lengths(V_vis,F_vis,l);
l /= (V_vis.colwise().maxCoeff() - V_vis.colwise().minCoeff()).norm();
const double max_l = 0.03;
if(l.maxCoeff()<max_l)
{
break;
}
Array<bool,Dynamic,1> bad = l.array().rowwise().maxCoeff() > max_l;
MatrixXi F_vis_bad, F_vis_good;
igl::slice_mask(F_vis,bad,1,F_vis_bad);
igl::slice_mask(F_vis,(bad!=true).eval(),1,F_vis_good);
igl::upsample(V_vis,F_vis_bad);
F_vis = igl::cat(1,F_vis_bad,F_vis_good);
}
}
// Compute signed distance
VectorXd S_vis;
if (!useFastWindingNumber)
{
VectorXi I;
MatrixXd N,C;
// Bunny is a watertight mesh so use pseudonormal for signing
signed_distance_pseudonormal(V_vis,V,F,tree,FN,VN,EN,EMAP,S_vis,I,C,N);
} else {
signed_distance_fast_winding_number(V_vis, V, F, tree, fwn_bvh, S_vis);
}
const auto & append_mesh = [&F_vis,&V_vis](
const Eigen::MatrixXd & V,
const Eigen::MatrixXi & F,
const RowVector3d & color)
{
F_vis.conservativeResize(F_vis.rows()+F.rows(),3);
F_vis.bottomRows(F.rows()) = F.array()+V_vis.rows();
V_vis.conservativeResize(V_vis.rows()+V.rows(),3);
V_vis.bottomRows(V.rows()) = V;
};
if(overlay)
{
append_mesh(V,F,RowVector3d(0.8,0.8,0.8));
}
viewer.data().clear();
viewer.data().set_mesh(V_vis,F_vis);
viewer.data().set_data(S_vis);
viewer.core().lighting_factor = overlay;
}
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int mod)
{
switch(key)
{
default:
return false;
case ' ':
overlay ^= true;
break;
case '.':
slice_z = std::min(slice_z+0.01,0.99);
break;
case ',':
slice_z = std::max(slice_z-0.01,0.01);
break;
case '1':
useFastWindingNumber = true;
break;
case '2':
useFastWindingNumber = false;
break;
}
update_visualization(viewer);
return true;
}
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
cout<<"Usage:"<<endl;
cout<<"[space] toggle showing surface."<<endl;
cout<<"'.'/',' push back/pull forward slicing plane."<<endl;
cout<< "1/2 toggle between fast winding number (1) and pseudonormal (2) signing. \n";
cout<<endl;
// Load mesh: (V,T) tet-mesh of convex hull, F contains original surface
// triangles
igl::readMESH(TUTORIAL_SHARED_PATH "/bunny.mesh",V,T,F);
// Encapsulated call to point_mesh_squared_distance to determine bounds
{
VectorXd sqrD;
VectorXi I;
MatrixXd C;
igl::point_mesh_squared_distance(V,V,F,sqrD,I,C);
max_distance = sqrt(sqrD.maxCoeff());
}
// Fast winding and Pseudo normal depend on differnt AABB trees... We initialize both here.
// Pseudonormal setup...
// Precompute signed distance AABB tree
tree.init(V,F);
// Precompute vertex,edge and face normals
igl::per_face_normals(V,F,FN);
igl::per_vertex_normals(
V,F,igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE,FN,VN);
igl::per_edge_normals(
V,F,igl::PER_EDGE_NORMALS_WEIGHTING_TYPE_UNIFORM,FN,EN,E,EMAP);
// fast winding number setup (just init fwn bvh)
igl::fast_winding_number(V, F, 2, fwn_bvh);
// Plot the generated mesh
igl::opengl::glfw::Viewer viewer;
update_visualization(viewer);
viewer.callback_key_down = &key_down;
viewer.data().show_lines = false;
viewer.launch();
}
igl::marching_cubes(S,GV,nx,ny,nz,V,F);
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
using namespace Eigen;
using namespace std;
using namespace igl;
MatrixXi F;
MatrixXd V;
// Read in inputs as double precision floating point meshes
read_triangle_mesh(
TUTORIAL_SHARED_PATH "/armadillo.obj",V,F);
cout<<"Creating grid..."<<endl;
// number of vertices on the largest side
const int s = 100;
// create grid
MatrixXd GV;
Eigen::RowVector3i res;
igl::voxel_grid(V,0,s,1,GV,res);
// compute values
cout<<"Computing distances..."<<endl;
VectorXd S,B;
{
VectorXi I;
MatrixXd C,N;
signed_distance(GV,V,F,SIGNED_DISTANCE_TYPE_PSEUDONORMAL,S,I,C,N);
// Convert distances to binary inside-outside data --> aliasing artifacts
B = S;
for_each(B.data(),B.data()+B.size(),[](double& b){b=(b>0?1:(b<0?-1:0));});
}
cout<<"Marching cubes..."<<endl;
MatrixXd SV,BV;
MatrixXi SF,BF;
igl::marching_cubes(S,GV,res(0),res(1),res(2),0,SV,SF);
igl::marching_cubes(B,GV,res(0),res(1),res(2),0,BV,BF);
cout<<R"(Usage:
'1' Show original mesh.
'2' Show marching cubes contour of signed distance.
'3' Show marching cubes contour of indicator function.
)";
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(SV,SF);
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer & viewer, unsigned char key, int mod)->bool
{
switch(key)
{
default:
return false;
case '1':
viewer.data().clear();
viewer.data().set_mesh(V,F);
break;
case '2':
viewer.data().clear();
viewer.data().set_mesh(SV,SF);
break;
case '3':
viewer.data().clear();
viewer.data().set_mesh(BV,BF);
break;
}
viewer.data().set_face_based(true);
return true;
};
viewer.launch();
}
(示例705)对到输入网格(左)的符号距离进行采样,然后使用行进立方体重建表面以勾勒出0级集(中心)的轮廓。为了进行比较,将此有符号距离场固定到指示器函数并轮廓显示严重的混叠伪影。
igl::bfs_orient(F,FF,C);
igl::embree::reorient_facets_raycast(V,F,FF,I);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
igl::opengl::glfw::Viewer viewer;
Eigen::MatrixXd V;
std::vector<Eigen::VectorXi> C(2);
std::vector<Eigen::MatrixXd> RGBcolors(2);
Eigen::MatrixXi F;
std::vector<Eigen::MatrixXi> FF(2);
bool is_showing_reoriented = false;
bool facetwise = false;
int main(int argc, char * argv[])
{
using namespace std;
cout<<R"(
Usage:
[space] Toggle between original and reoriented faces
F,f Toggle between patchwise and facetwise reorientation
S,s Scramble colors
)";
igl::read_triangle_mesh(TUTORIAL_SHARED_PATH "/truck.obj",V,F);
const auto & scramble_colors = []()
{
for(int pass = 0;pass<2;pass++)
{
Eigen::MatrixXi R;
igl::randperm(C[pass].maxCoeff()+1,R);
C[pass] = igl::slice(R,Eigen::MatrixXi(C[pass]));
Eigen::MatrixXd HSV(C[pass].rows(),3);
HSV.col(0) =
360.*C[pass].array().cast<double>()/(double)C[pass].maxCoeff();
HSV.rightCols(2).setConstant(1.0);
igl::hsv_to_rgb(HSV,RGBcolors[pass]);
}
viewer.data().set_colors(RGBcolors[facetwise]);
};
viewer.callback_key_pressed =
[&scramble_colors]
(igl::opengl::glfw::Viewer& /*viewer*/, unsigned int key, int mod)->bool
{
switch(key)
{
default:
return false;
case 'F':
case 'f':
{
facetwise = !facetwise;
break;
}
case 'S':
case 's':
{
scramble_colors();
return true;
}
case ' ':
{
is_showing_reoriented = !is_showing_reoriented;
break;
}
}
viewer.data().clear();
viewer.data().set_mesh(V,is_showing_reoriented?FF[facetwise]:F);
viewer.data().set_colors(RGBcolors[facetwise]);
return true;
};
// Compute patches
for(int pass = 0;pass<2;pass++)
{
Eigen::VectorXi I;
igl::embree::reorient_facets_raycast(
V,F,F.rows()*100,10,pass==1,false,false,I,C[pass]);
// apply reorientation
FF[pass].conservativeResize(F.rows(),F.cols());
for(int i = 0;i<I.rows();i++)
{
if(I(i))
{
FF[pass].row(i) = (F.row(i).reverse()).eval();
}else
{
FF[pass].row(i) = F.row(i);
}
}
}
viewer.data().set_mesh(V,is_showing_reoriented?FF[facetwise]:F);
viewer.data().set_face_based(true);
scramble_colors();
viewer.launch();
}
(示例 706)加载方向不一致的卡车模型(背面三角形显示较暗)。可定向的斑块具有独特的颜色,然后朝外(左中)。或者,每个单独的三角形都被视为一个“补丁”(中间右边)并独立地向外定向。
移动固体物体 A 的扫描体积 S 可以定义为空间中的任何点,使得该点在某个时刻位于固体内部。换句话说,它是刚性运动 f(t) 随时间变化的固体物体的结合:
由三角形网格以非平凡旋转的刚性运动为界的实体的扫掠体积表面不是由三角形网格精确表示的表面:它将是一个分段规则表面。
要看到这一点,请考虑在执行螺钉运动时被单个边的线段扫描的曲面。
这意味着,如果我们想让三角形网格体的扫描体积表面经历刚性运动,并且我们希望输出是另一个三角形网格,那么我们必须对一定程度的近似误差感到满意。
如果是一个三角形网格,那么 ∂A 我们可以通过以下方式近似:1)在有限的步长 [0,Δt,2Δt,…,1] 上离散时间和2)使用规则网格离散空间并使用网格值的三线性插值表示距离场。最后, ∂S 输出网格通过使用行进立方体 42 进行轮廓绘制来近似。
这种方法类似于Schroeder等人在1994年描述的方法 44 ,以及Garg等人在2016年与布尔运算结合使用的方法 38 。
在 libigl 中,如果输入实体的表面由 表示 (V,F) ,则输出表面网格将在调用后: (SV,SF)
igl::copyleft::swept_volume(V,F,num_time_steps,grid_size,isolevel,SV,SF);
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
using namespace std;
using namespace igl;
Eigen::MatrixXi F,SF;
Eigen::MatrixXd V,SV,VT;
bool show_swept_volume = false;
// Define a rigid motion
const auto & transform = [](const double t)->Eigen::Affine3d
{
Eigen::Affine3d T = Eigen::Affine3d::Identity();
T.rotate(Eigen::AngleAxisd(t*2.*igl::PI,Eigen::Vector3d(0,1,0)));
T.translate(Eigen::Vector3d(0,0.125*cos(2.*igl::PI*t),0));
return T;
};
// Read in inputs as double precision floating point meshes
read_triangle_mesh(
TUTORIAL_SHARED_PATH "/bunny.off",V,F);
cout<<R"(Usage:
[space] Toggle between transforming original mesh and swept volume
)";
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
viewer.data().set_face_based(true);
viewer.core().is_animating = !show_swept_volume;
const int grid_size = 50;
const int time_steps = 200;
const double isolevel = 0.1;
std::cerr<<"Computing swept volume...";
igl::swept_volume(
V,F,transform,time_steps,grid_size,isolevel,SV,SF);
std::cerr<<" finished."<<std::endl;
viewer.callback_pre_draw =
[&](igl::opengl::glfw::Viewer & viewer)->bool
{
if(!show_swept_volume)
{
Eigen::Affine3d T = transform(0.25*igl::get_seconds());
VT = V*T.matrix().block(0,0,3,3).transpose();
Eigen::RowVector3d trans = T.matrix().block(0,3,3,1).transpose();
VT = ( VT.rowwise() + trans).eval();
viewer.data().set_vertices(VT);
viewer.data().compute_normals();
}
return false;
};
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer & viewer, unsigned char key, int mod)->bool
{
switch(key)
{
default:
return false;
case ' ':
show_swept_volume = !show_swept_volume;
viewer.data().clear();
if(show_swept_volume)
{
viewer.data().set_mesh(SV,SF);
Eigen::Vector3d ambient = Eigen::Vector3d(SILVER_AMBIENT[0], SILVER_AMBIENT[1], SILVER_AMBIENT[2]);
Eigen::Vector3d diffuse = Eigen::Vector3d(SILVER_DIFFUSE[0], SILVER_DIFFUSE[1], SILVER_DIFFUSE[2]);
Eigen::Vector3d specular = Eigen::Vector3d(SILVER_SPECULAR[0], SILVER_SPECULAR[1], SILVER_SPECULAR[2]);
viewer.data().uniform_colors(ambient,diffuse,specular);
}
else
{
viewer.data().set_mesh(V,F);
}
viewer.core().is_animating = !show_swept_volume;
viewer.data().set_face_based(true);
break;
}
return true;
};
viewer.launch();
}
(示例707)计算兔子模型经历刚性运动(金)的扫描体积(银)的表面
bool hit = igl::unproject_onto_mesh(
Vector2f(x,y),
F,
viewer.core.view * viewer.core.model,
viewer.core.proj,
viewer.core.viewport,
*ei,
fid,
bc);
#include
#include
#include
#include
int main(int argc, char *argv[])
{
// Mesh with per-face color
Eigen::MatrixXd V, C;
Eigen::MatrixXi F;
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/fertility.off", V, F);
// Initialize white
C = Eigen::MatrixXd::Constant(F.rows(),3,1);
igl::opengl::glfw::Viewer viewer;
viewer.callback_mouse_down =
[&V,&F,&C](igl::opengl::glfw::Viewer& viewer, int, int)->bool
{
int fid;
Eigen::Vector3f bc;
// Cast a ray in the view direction starting from the mouse position
double x = viewer.current_mouse_x;
double y = viewer.core().viewport(3) - viewer.current_mouse_y;
if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core().view,
viewer.core().proj, viewer.core().viewport, V, F, fid, bc))
{
// paint hit red
C.row(fid)<<1,0,0;
viewer.data().set_colors(C);
return true;
}
return false;
};
std::cout<<R"(Usage:
[click] Pick face on shape
)";
// Show mesh
viewer.data().set_mesh(V, F);
viewer.data().set_colors(C);
viewer.data().show_lines = false;
viewer.launch();
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace Eigen;
void check_mesh_for_issues(Eigen::MatrixXd& V, Eigen::MatrixXi& F);
void param_2d_demo_iter(igl::opengl::glfw::Viewer& viewer);
void get_soft_constraint_for_circle(Eigen::MatrixXd& V_o, Eigen::MatrixXi& F, Eigen::VectorXi& b, Eigen::MatrixXd& bc);
void soft_const_demo_iter(igl::opengl::glfw::Viewer& viewer);
void deform_3d_demo_iter(igl::opengl::glfw::Viewer& viewer);
void get_cube_corner_constraints(Eigen::MatrixXd& V_o, Eigen::MatrixXi& F, Eigen::VectorXi& b, Eigen::MatrixXd& bc);
void display_3d_mesh(igl::opengl::glfw::Viewer& viewer);
void int_set_to_eigen_vector(const std::set<int>& int_set, Eigen::VectorXi& vec);
Eigen::MatrixXd V;
Eigen::MatrixXi F;
bool first_iter = true;
igl::SLIMData sData;
igl::Timer timer;
double uv_scale_param;
enum DEMO_TYPE {
PARAM_2D,
SOFT_CONST,
DEFORM_3D
};
DEMO_TYPE demo_type;
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier){
if (key == ' ') {
switch (demo_type) {
case PARAM_2D: {
param_2d_demo_iter(viewer);
break;
}
case SOFT_CONST: {
soft_const_demo_iter(viewer);
break;
}
case DEFORM_3D: {
deform_3d_demo_iter(viewer);
break;
}
default:
break;
}
}
return false;
}
void param_2d_demo_iter(igl::opengl::glfw::Viewer& viewer) {
if (first_iter) {
timer.start();
igl::read_triangle_mesh(TUTORIAL_SHARED_PATH "/face.obj", V, F);
check_mesh_for_issues(V,F);
cout << "\tMesh is valid!" << endl;
Eigen::MatrixXd uv_init;
Eigen::VectorXi bnd; Eigen::MatrixXd bnd_uv;
igl::boundary_loop(F,bnd);
igl::map_vertices_to_circle(V,bnd,bnd_uv);
igl::harmonic(V,F,bnd,bnd_uv,1,uv_init);
if (igl::flipped_triangles(uv_init,F).size() != 0) {
igl::harmonic(F,bnd,bnd_uv,1,uv_init); // use uniform laplacian
}
cout << "initialized parametrization" << endl;
sData.slim_energy = igl::MappingEnergyType::SYMMETRIC_DIRICHLET;
Eigen::VectorXi b; Eigen::MatrixXd bc;
slim_precompute(V,F,uv_init,sData, igl::MappingEnergyType::SYMMETRIC_DIRICHLET, b,bc,0);
uv_scale_param = 15 * (1./sqrt(sData.mesh_area));
viewer.data().set_mesh(V, F);
viewer.core().align_camera_center(V,F);
viewer.data().set_uv(sData.V_o*uv_scale_param);
viewer.data().compute_normals();
viewer.data().show_texture = true;
first_iter = false;
} else {
timer.start();
slim_solve(sData,1); // 1 iter
viewer.data().set_uv(sData.V_o*uv_scale_param);
}
cout << "time = " << timer.getElapsedTime() << endl;
cout << "energy = " << sData.energy << endl;
}
void soft_const_demo_iter(igl::opengl::glfw::Viewer& viewer) {
if (first_iter) {
igl::read_triangle_mesh(TUTORIAL_SHARED_PATH "/circle.obj", V, F);
check_mesh_for_issues(V,F);
cout << "\tMesh is valid!" << endl;
Eigen::MatrixXd V_0 = V.block(0,0,V.rows(),2);
Eigen::VectorXi b; Eigen::MatrixXd bc;
get_soft_constraint_for_circle(V_0,F,b,bc);
double soft_const_p = 1e5;
slim_precompute(V,F,V_0,sData,igl::MappingEnergyType::SYMMETRIC_DIRICHLET,b,bc,soft_const_p);
viewer.data().set_mesh(V, F);
viewer.core().align_camera_center(V,F);
viewer.data().compute_normals();
viewer.data().show_lines = true;
first_iter = false;
} else {
slim_solve(sData,1); // 1 iter
viewer.data().set_mesh(sData.V_o, F);
}
}
void deform_3d_demo_iter(igl::opengl::glfw::Viewer& viewer) {
if (first_iter) {
timer.start();
igl::readOBJ(TUTORIAL_SHARED_PATH "/cube_40k.obj", V, F);
Eigen::MatrixXd V_0 = V;
Eigen::VectorXi b; Eigen::MatrixXd bc;
get_cube_corner_constraints(V_0,F,b,bc);
double soft_const_p = 1e5;
sData.exp_factor = 5.0;
slim_precompute(V,F,V_0,sData,igl::MappingEnergyType::EXP_CONFORMAL,b,bc,soft_const_p);
//cout << "precomputed" << endl;
first_iter = false;
display_3d_mesh(viewer);
} else {
timer.start();
slim_solve(sData,1); // 1 iter
display_3d_mesh(viewer);
}
cout << "time = " << timer.getElapsedTime() << endl;
cout << "energy = " << sData.energy << endl;
}
void display_3d_mesh(igl::opengl::glfw::Viewer& viewer) {
MatrixXd V_temp; MatrixXi F_temp;
Eigen::MatrixXd Barycenters;
igl::barycenter(sData.V,sData.F,Barycenters);
//cout << "Barycenters.rows() = " << Barycenters.rows() << endl;
//double t = double((key - '1')+1) / 9.0;
double view_depth = 10.;
double t = view_depth/9.;
VectorXd v = Barycenters.col(2).array() - Barycenters.col(2).minCoeff();
v /= v.col(0).maxCoeff();
vector<int> s;
for (unsigned i=0; i<v.size();++i)
if (v(i) < t)
s.push_back(i);
V_temp.resize(s.size()*4,3);
F_temp.resize(s.size()*4,3);
for (unsigned i=0; i<s.size();++i){
V_temp.row(i*4+0) = sData.V_o.row(sData.F(s[i],0));
V_temp.row(i*4+1) = sData.V_o.row(sData.F(s[i],1));
V_temp.row(i*4+2) = sData.V_o.row(sData.F(s[i],2));
V_temp.row(i*4+3) = sData.V_o.row(sData.F(s[i],3));
F_temp.row(i*4+0) << (i*4)+0, (i*4)+1, (i*4)+3;
F_temp.row(i*4+1) << (i*4)+0, (i*4)+2, (i*4)+1;
F_temp.row(i*4+2) << (i*4)+3, (i*4)+2, (i*4)+0;
F_temp.row(i*4+3) << (i*4)+1, (i*4)+2, (i*4)+3;
}
viewer.data().set_mesh(V_temp,F_temp);
viewer.core().align_camera_center(V_temp,F_temp);
viewer.data().set_face_based(true);
viewer.data().show_lines = true;
}
int main(int argc, char *argv[]) {
cerr << "Press space for running an iteration." << std::endl;
cerr << "Syntax: " << argv[0] << " demo_number (1 to 3)" << std::endl;
cerr << "1. 2D unconstrained parametrization" << std::endl;
cerr << "2. 2D deformation with positional constraints" << std::endl;
cerr << "3. 3D mesh deformation with positional constraints" << std::endl;
demo_type = PARAM_2D;
if (argc == 2) {
switch (std::atoi(argv[1])) {
case 1: {
demo_type = PARAM_2D;
break;
} case 2: {
demo_type = SOFT_CONST;
break;
} case 3: {
demo_type = DEFORM_3D;
break;
}
default: {
cerr << "Wrong demo number - Please choose one between 1-3" << std:: endl;
exit(1);
}
}
}
// Launch the viewer
igl::opengl::glfw::Viewer viewer;
viewer.callback_key_down = &key_down;
// Disable wireframe
viewer.data().show_lines = false;
// Draw checkerboard texture
viewer.data().show_texture = false;
// First iteration
key_down(viewer, ' ', 0);
viewer.launch();
return 0;
}
void check_mesh_for_issues(Eigen::MatrixXd& V, Eigen::MatrixXi& F) {
Eigen::SparseMatrix<double> A;
igl::adjacency_matrix(F,A);
Eigen::MatrixXi C, Ci;
igl::vertex_components(A, C, Ci);
int connected_components = Ci.rows();
if (connected_components!=1) {
cout << "Error! Input has multiple connected components" << endl; exit(1);
}
int euler_char = igl::euler_characteristic(V, F);
if (euler_char!=1)
{
cout <<
"Error! Input does not have a disk topology, it's euler char is " <<
euler_char << endl;
exit(1);
}
bool is_edge_manifold = igl::is_edge_manifold(F);
if (!is_edge_manifold) {
cout << "Error! Input is not an edge manifold" << endl; exit(1);
}
Eigen::VectorXd areas; igl::doublearea(V,F,areas);
const double eps = 1e-14;
for (int i = 0; i < areas.rows(); i++) {
if (areas(i) < eps) {
cout << "Error! Input has zero area faces" << endl; exit(1);
}
}
}
void get_soft_constraint_for_circle(Eigen::MatrixXd& V_o, Eigen::MatrixXi& F, Eigen::VectorXi& b, Eigen::MatrixXd& bc) {
Eigen::VectorXi bnd;
igl::boundary_loop(F,bnd);
const int B_STEPS = 22; // constraint every B_STEPS vertices of the boundary
b.resize(bnd.rows()/B_STEPS);
bc.resize(b.rows(),2);
int c_idx = 0;
for (int i = B_STEPS; i < bnd.rows(); i+=B_STEPS) {
b(c_idx) = bnd(i);
c_idx++;
}
bc.row(0) = V_o.row(b(0)); // keep it there for now
bc.row(1) = V_o.row(b(2));
bc.row(2) = V_o.row(b(3));
bc.row(3) = V_o.row(b(4));
bc.row(4) = V_o.row(b(5));
bc.row(0) << V_o(b(0),0), 0;
bc.row(4) << V_o(b(4),0), 0;
bc.row(2) << V_o(b(2),0), 0.1;
bc.row(3) << V_o(b(3),0), 0.05;
bc.row(1) << V_o(b(1),0), -0.15;
bc.row(5) << V_o(b(5),0), +0.15;
}
void get_cube_corner_constraints(Eigen::MatrixXd& V, Eigen::MatrixXi& F, Eigen::VectorXi& b, Eigen::MatrixXd& bc) {
double min_x,max_x,min_y,max_y,min_z,max_z;
min_x = V.col(0).minCoeff(); max_x = V.col(0).maxCoeff();
min_y = V.col(1).minCoeff(); max_y = V.col(1).maxCoeff();
min_z = V.col(2).minCoeff(); max_z = V.col(2).maxCoeff();
// get all cube corners
b.resize(8,1); bc.resize(8,3);
int x;
for (int i = 0; i < V.rows(); i++) {
if (V.row(i) == Eigen::RowVector3d(min_x,min_y,min_z)) b(0) = i;
if (V.row(i) == Eigen::RowVector3d(min_x,min_y,max_z)) b(1) = i;
if (V.row(i) == Eigen::RowVector3d(min_x,max_y,min_z)) b(2) = i;
if (V.row(i) == Eigen::RowVector3d(min_x,max_y,max_z)) b(3) = i;
if (V.row(i) == Eigen::RowVector3d(max_x,min_y,min_z)) b(4) = i;
if (V.row(i) == Eigen::RowVector3d(max_x,max_y,min_z)) b(5) = i;
if (V.row(i) == Eigen::RowVector3d(max_x,min_y,max_z)) b(6) = i;
if (V.row(i) == Eigen::RowVector3d(max_x,max_y,max_z)) b(7) = i;
}
// get all cube edges
std::set<int> cube_edge1; Eigen::VectorXi cube_edge1_vec;
for (int i = 0; i < V.rows(); i++) {
if ((V(i,0) == min_x && V(i,1) == min_y)) {
cube_edge1.insert(i);
}
}
Eigen::VectorXi edge1;
int_set_to_eigen_vector(cube_edge1, edge1);
std::set<int> cube_edge2; Eigen::VectorXi edge2;
for (int i = 0; i < V.rows(); i++) {
if ((V(i,0) == max_x && V(i,1) == max_y)) {
cube_edge2.insert(i);
}
}
int_set_to_eigen_vector(cube_edge2, edge2);
b = igl::cat(1,edge1,edge2);
std::set<int> cube_edge3; Eigen::VectorXi edge3;
for (int i = 0; i < V.rows(); i++) {
if ((V(i,0) == max_x && V(i,1) == min_y)) {
cube_edge3.insert(i);
}
}
int_set_to_eigen_vector(cube_edge3, edge3);
b = igl::cat(1,b,edge3);
std::set<int> cube_edge4; Eigen::VectorXi edge4;
for (int i = 0; i < V.rows(); i++) {
if ((V(i,0) == min_x && V(i,1) == max_y)) {
cube_edge4.insert(i);
}
}
int_set_to_eigen_vector(cube_edge4, edge4);
b = igl::cat(1,b,edge4);
bc.resize(b.rows(),3);
Eigen::Matrix3d m; m = Eigen::AngleAxisd(0.3 * igl::PI, Eigen::Vector3d(1./sqrt(2.),1./sqrt(2.),0.)/*Eigen::Vector3d::UnitX()*/);
int i = 0;
for (; i < cube_edge1.size(); i++) {
Eigen::RowVector3d edge_rot_center(min_x,min_y,(min_z+max_z)/2.);
bc.row(i) = (V.row(b(i)) - edge_rot_center) * m + edge_rot_center;
}
for (; i < cube_edge1.size() + cube_edge2.size(); i++) {
Eigen::RowVector3d edge_rot_center(max_x,max_y,(min_z+max_z)/2.);
bc.row(i) = (V.row(b(i)) - edge_rot_center) * m.transpose() + edge_rot_center;
}
for (; i < cube_edge1.size() + cube_edge2.size() + cube_edge3.size(); i++) {
bc.row(i) = 0.75*V.row(b(i));
}
for (; i < b.rows(); i++) {
bc.row(i) = 0.75*V.row(b(i));
}
}
void int_set_to_eigen_vector(const std::set<int>& int_set, Eigen::VectorXi& vec) {
vec.resize(int_set.size()); int idx = 0;
for(auto f : int_set) {
vec(idx) = f; idx++;
}
}
使用 SLIM 算法在 10 次迭代中计算具有 50k 面的网格的局部注入参数化。
......
给定一个带有顶点和面的粗网格(又名笼子),可以通过细分每个面来创建具有更多顶点 V 和面 F 的更高分辨率网格。也就是说,输入中的每个粗三角形都被许多较小的三角形所取代。Libigl 有三种不同的方法来细分三角形网格。
“平面内”细分方法不会改变网格的点集或载体表面。在现有三角形的平面上添加新顶点,并且不会移动从原始网格中幸存下来的顶点。
通过添加新面,细分算法会改变网格的组合。组合数学的变化和高分辨率顶点定位公式称为“细分规则”。
711 案例完整展示:
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
using namespace std;
using namespace igl;
Eigen::MatrixXi OF,F;
Eigen::MatrixXd OV,V;
bool show_swept_volume = false;
read_triangle_mesh(
TUTORIAL_SHARED_PATH "/decimated-knight.off",OV,OF);
V = OV;
F = OF;
cout<<R"(Usage:
1 Restore Original mesh
2 Apply In-plane upsampled mesh
3 Apply Loop subdivided mesh
4 Apply False barycentric subdivision
)";
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
viewer.data().set_face_based(true);
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer & viewer, unsigned char key, int mod)->bool
{
switch(key)
{
default:
return false;
case '1':
{
V = OV;
F = OF;
break;
}
case '2':
{
igl::upsample( Eigen::MatrixXd(V), Eigen::MatrixXi(F), V,F);
break;
}
case '3':
{
igl::loop( Eigen::MatrixXd(V), Eigen::MatrixXi(F), V,F);
break;
}
case '4':
{
igl::false_barycentric_subdivision(
Eigen::MatrixXd(V),Eigen::MatrixXi(F),V,F);
break;
}
}
viewer.data().clear();
viewer.data().set_mesh(V,F);
viewer.data().set_face_based(true);
return true;
};
viewer.launch();
}
原始粗网格和三种不同的细分方法: igl::upsample 、 igl::loop 和 igl::false_barycentric_subdivision
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
typedef Eigen::SparseMatrix<double> SparseMat;
srand(57);
//Read our mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;
if(!igl::read_triangle_mesh(
argc>1?argv[1]: TUTORIAL_SHARED_PATH "/beetle.off",V,F)) {
std::cout << "Failed to load mesh." << std::endl;
}
//Constructing an exact function to smooth
igl::HeatGeodesicsData<double> hgData;
igl::heat_geodesics_precompute(V, F, hgData);
Eigen::VectorXd heatDist;
Eigen::VectorXi gamma(1); gamma << 1947; //1631;
igl::heat_geodesics_solve(hgData, gamma, heatDist);
Eigen::VectorXd zexact =
0.1*(heatDist.array() + (-heatDist.maxCoeff())).pow(2)
+ 3*V.block(0,1,V.rows(),1).array().cos();
//Make the exact function noisy
const double s = 0.1*(zexact.maxCoeff() - zexact.minCoeff());
Eigen::VectorXd znoisy = zexact + s*Eigen::VectorXd::Random(zexact.size());
//Constructing the squared Laplacian and squared Hessian energy
SparseMat L, M;
igl::cotmatrix(V, F, L);
igl::massmatrix(V, F, igl::MASSMATRIX_TYPE_BARYCENTRIC, M);
Eigen::SimplicialLDLT<SparseMat> solver(M);
SparseMat MinvL = solver.solve(L);
SparseMat QL = L.transpose()*MinvL;
SparseMat QH;
igl::hessian_energy(V, F, QH);
SparseMat QcH;
igl::curved_hessian_energy(V, F, QcH);
//Solve to find Laplacian-smoothed Hessian-smoothed, and
// curved-Hessian-smoothed solutions
const double al = 3e-7;
Eigen::SimplicialLDLT<SparseMat> lapSolver(al*QL + (1.-al)*M);
Eigen::VectorXd zl = lapSolver.solve(al*M*znoisy);
const double ah = 2e-7;
Eigen::SimplicialLDLT<SparseMat> hessSolver(ah*QH + (1.-ah)*M);
Eigen::VectorXd zh = hessSolver.solve(ah*M*znoisy);
const double ach = 3e-7;
Eigen::SimplicialLDLT<SparseMat> curvedHessSolver(al*QcH + (1.-ach)*M);
Eigen::VectorXd zch = curvedHessSolver.solve(ach*M*znoisy);
//Viewer that shows all functions: zexact, znoisy, zl, zh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
viewer.data().show_lines = false;
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer & viewer, unsigned char key, int mod)->bool
{
//Graduate result to show isolines, then compute color matrix
const Eigen::VectorXd* z;
switch(key) {
case '1':
z = &zexact;
break;
case '2':
z = &znoisy;
break;
case '3':
z = &zl;
break;
case '4':
z = &zh;
break;
case '5':
z = &zch;
break;
default:
return false;
}
viewer.data().set_data(*z);
return true;
};
std::cout << R"(Smoothing a noisy function.
Usage:
1 Show original function
2 Show noisy function
3 Biharmonic smoothing (zero Neumann boundary)
4 Biharmonic smoothing (natural planar Hessian boundary)
5 Biharmonic smoothing (natural curved Hessian boundary)
)";
Eigen::MatrixXd CM;
igl::parula(Eigen::VectorXd::LinSpaced(21,0,1).eval(),false,CM);
igl::isolines_map(Eigen::MatrixXd(CM),CM);
viewer.data().set_colormap(CM);
viewer.data().set_data(znoisy);
viewer.launch();
//Constructing a step function to smooth
Eigen::VectorXd zstep = Eigen::VectorXd::Zero(V.rows());
for(int i=0; i<V.rows(); ++i) {
zstep(i) = V(i,2)<-0.25 ? 1. : (V(i,2)>0.31 ? 2. : 0);
}
//Smooth that function
const double sl = 2e-5;
Eigen::SimplicialLDLT<SparseMat> stepLapSolver(sl*QL + (1.-sl)*M);
Eigen::VectorXd stepzl = stepLapSolver.solve(al*M*zstep);
const double sh = 6e-6;
Eigen::SimplicialLDLT<SparseMat> stepHessSolver(sh*QH + (1.-sh)*M);
Eigen::VectorXd stepzh = stepHessSolver.solve(ah*M*zstep);
const double sch = 2e-5;
Eigen::SimplicialLDLT<SparseMat> stepCurvedHessSolver(sl*QcH + (1.-sch)*M);
Eigen::VectorXd stepzch = stepCurvedHessSolver.solve(ach*M*zstep);
//Display functions
igl::opengl::glfw::Viewer viewer2;
viewer2.data().set_mesh(V,F);
viewer2.data().show_lines = false;
viewer2.callback_key_down =
[&](igl::opengl::glfw::Viewer & viewer, unsigned char key, int mod)->bool
{
//Graduate result to show isolines, then compute color matrix
const Eigen::VectorXd* z;
switch(key) {
case '1':
z = &zstep;
break;
case '2':
z = &stepzl;
break;
case '3':
z = &stepzh;
break;
case '4':
z = &stepzch;
break;
default:
return false;
}
viewer.data().set_data(*z);
return true;
};
std::cout << R"(Smoothing a step function.
Usage:
1 Show step function
2 Biharmonic smoothing (zero Neumann boundary)
3 Biharmonic smoothing (natural planar Hessian boundary)
4 Biharmonic smoothing (natural curved Hessian boundary)
)";
viewer2.data().set_colormap(CM);
viewer2.data().set_data(zstep);
viewer2.launch();
return 0;
}
例 712)从左到右:甲虫网格上的函数,增加噪声的函数,拉普拉斯能和零诺依曼边界条件平滑的结果,以及黑森能量平滑的结果
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Quad mesh loaded
Eigen::MatrixXd VQC;
Eigen::MatrixXi FQC;
Eigen::MatrixXi E;
Eigen::MatrixXi FQCtri;
Eigen::MatrixXd PQC0, PQC1, PQC2, PQC3;
// Euclidean-regular quad mesh
Eigen::MatrixXd VQCregular;
Eigen::MatrixXi FQCtriregular;
Eigen::MatrixXd PQC0regular, PQC1regular, PQC2regular, PQC3regular;
igl::ShapeupData su_data;
// Scale for visualizing the fields
double global_scale; //TODO: not used
void quadAngleRegularity(const Eigen::MatrixXd& V, const Eigen::MatrixXi& Q, Eigen::VectorXd& angleRegularity)
{
angleRegularity.conservativeResize(Q.rows());
angleRegularity.setZero();
for (int i=0;i<Q.rows();i++){
for (int j=0;j<4;j++){
Eigen::RowVectorXd v21=(V.row(Q(i,j))-V.row(Q(i,(j+1)%4))).normalized();
Eigen::RowVectorXd v23=(V.row(Q(i,(j+2)%4))-V.row(Q(i,(j+1)%4))).normalized();
angleRegularity(i)+=(abs(acos(v21.dot(v23))-igl::PI/2.0)/(igl::PI/2.0))/4.0;
}
}
}
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
{
using namespace std;
using namespace Eigen;
// Plot the original quad mesh
if (key == '1')
{
viewer.data().clear();
// Draw the triangulated quad mesh
viewer.data().set_mesh(VQC, FQCtri);
// Assign a color to each quad that corresponds to the average deviation of each angle from pi/2
VectorXd angleRegularity(FQC.rows());
quadAngleRegularity( VQC, FQC, angleRegularity);
MatrixXd Ct;
igl::jet(angleRegularity, 0.0, 0.05, Ct);
MatrixXd C(FQCtri.rows(),3);
C << Ct, Ct;
viewer.data().set_colors(C);
// Plot a line for each edge of the quad mesh
viewer.data().add_edges(PQC0, PQC1, Eigen::RowVector3d(0,0,0));
viewer.data().add_edges(PQC1, PQC2, Eigen::RowVector3d(0,0,0));
viewer.data().add_edges(PQC2, PQC3, Eigen::RowVector3d(0,0,0));
viewer.data().add_edges(PQC3, PQC0, Eigen::RowVector3d(0,0,0));
}
// Plot the planarized quad mesh
if (key == '2')
{
viewer.data().clear();
// Draw the triangulated quad mesh
viewer.data().set_mesh(VQCregular, FQCtri);
// Assign a color to each quad that corresponds to its planarity
VectorXd angleRegularity(FQC.rows());
quadAngleRegularity( VQCregular, FQC, angleRegularity);
MatrixXd Ct;
igl::jet(angleRegularity, 0, 0.05, Ct);
MatrixXd C(FQCtri.rows(),3);
C << Ct, Ct;
viewer.data().set_colors(C);
// Plot a line for each edge of the quad mesh
viewer.data().add_edges(PQC0regular, PQC1regular, Eigen::RowVector3d(0,0,0));
viewer.data().add_edges(PQC1regular, PQC2regular, Eigen::RowVector3d(0,0,0));
viewer.data().add_edges(PQC2regular, PQC3regular, Eigen::RowVector3d(0,0,0));
viewer.data().add_edges(PQC3regular, PQC0regular, Eigen::RowVector3d(0,0,0));
}
return false;
}
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
// Load a quad mesh
igl::readOFF(TUTORIAL_SHARED_PATH "/halftunnel.off", VQC, FQC);
// Convert it in a triangle mesh
FQCtri.resize(2*FQC.rows(), 3);
FQCtri << FQC.col(0),FQC.col(1),FQC.col(2),
FQC.col(2),FQC.col(3),FQC.col(0);
igl::slice( VQC, FQC.col(0).eval(), 1, PQC0);
igl::slice( VQC, FQC.col(1).eval(), 1, PQC1);
igl::slice( VQC, FQC.col(2).eval(), 1, PQC2);
igl::slice( VQC, FQC.col(3).eval(), 1, PQC3);
// Create a planar version with ShapeUp
//igl::planarize_quad_mesh(VQC, FQC, 100, 0.005, VQCregular);
E.resize(FQC.size(),2);
E.col(0)<<FQC.col(0),FQC.col(1),FQC.col(2),FQC.col(3);
E.col(1)<<FQC.col(1),FQC.col(2),FQC.col(3),FQC.col(0);
VectorXi b(1); b(0)=0; //setting the first vertex to be the same.
VectorXd wShape=VectorXd::Constant(FQC.rows(),1.0);
VectorXd wSmooth=VectorXd::Constant(E.rows(),1.0);
MatrixXd bc(1,3); bc<<VQC.row(0);
VectorXi array_of_fours=VectorXi::Constant(FQC.rows(),4);
igl::shapeup_projection_function localFunction(igl::shapeup_regular_face_projection);
su_data.maxIterations=200;
shapeup_precomputation(VQC, array_of_fours,FQC,E,b,wShape, wSmooth,su_data);
shapeup_solve(bc,localFunction, VQC,su_data, false,VQCregular);
// Convert the planarized mesh to triangles
igl::slice( VQCregular, FQC.col(0).eval(), 1, PQC0regular);
igl::slice( VQCregular, FQC.col(1).eval(), 1, PQC1regular);
igl::slice( VQCregular, FQC.col(2).eval(), 1, PQC2regular);
igl::slice( VQCregular, FQC.col(3).eval(), 1, PQC3regular);
// Launch the viewer
igl::opengl::glfw::Viewer viewer;
key_down(viewer,'1',0);
viewer.data().invert_normals = true;
viewer.data().show_lines = false;
viewer.callback_key_down = &key_down;
viewer.launch();
}
例713)半隧道网格(左)已优化为几乎完全规则(右)。色标介于 [0,0.05] 之间,测量每个面的角度与 的平均归一化偏差 90∘
igl::marching_tets(TV,TT,S, isovalue ,V,F);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
const auto & tictoc = []()
{
static double t_start = igl::get_seconds();
double diff = igl::get_seconds()-t_start;
t_start += diff;
return diff;
};
// Load a surface mesh which is a cube
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::read_triangle_mesh(
argc>1?argv[1]: TUTORIAL_SHARED_PATH "/armadillo.obj",V,F);
Eigen::RowVector3i side;
Eigen::MatrixXd TV;
tictoc();
igl::voxel_grid(V,0,100,1,TV,side);
printf("igl::voxel_grid %g secs\n",tictoc());
Eigen::MatrixXi TT5,TT6;
tictoc();
igl::tetrahedralized_grid(TV,side,igl::TETRAHEDRALIZED_GRID_TYPE_5,TT5);
printf("igl::tetrahedralized_grid %g secs\n",tictoc());
igl::tetrahedralized_grid(TV,side,igl::TETRAHEDRALIZED_GRID_TYPE_6_ROTATIONAL,TT6);
printf("igl::tetrahedralized_grid %g secs\n",tictoc());
tictoc();
Eigen::VectorXd S;
{
Eigen::VectorXi I;
Eigen::MatrixXd C,N;
igl::signed_distance(
TV,V,F,
igl::SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER,
std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
S,I,C,N);
}
printf("igl::signed_distance %g secs\n",tictoc());
std::vector<Eigen::MatrixXd> SV(3);
std::vector<Eigen::MatrixXi> SF(3);
igl::marching_tets(TV,TT5,S,0,SV[0],SF[0]);
printf("igl::marching_tets5 %g secs\n",tictoc());
igl::marching_tets(TV,TT6,S,0,SV[1],SF[1]);
printf("igl::marching_tets6 %g secs\n",tictoc());
tictoc();
igl::marching_cubes(S,TV,side(0),side(1),side(2),0,SV[2],SF[2]);
printf("igl::marching_cubes %g secs\n",tictoc());
//igl::writeOBJ("tmc.obj",SV,SF);
// Draw the mesh stored in (SV, SF)
igl::opengl::glfw::Viewer vr;
vr.data().set_mesh(V,F);
vr.data().is_visible = false;
vr.append_mesh();
int sel = 0;
const auto update = [&]()
{
vr.data().clear();
vr.data().set_mesh(SV[sel],SF[sel]);
};
vr.callback_key_pressed = [&](decltype(vr) &,unsigned int key, int mod)
{
switch(key)
{
case ',':
case '.':
sel = (sel+SV.size()+(key=='.'?1:-1))%SV.size();
update();
return true;
case ' ':
vr.data_list[0].is_visible = !vr.data_list[0].is_visible;
vr.data_list[1].is_visible = !vr.data_list[1].is_visible;
vr.selected_data_index = (vr.selected_data_index+1)%2;
return true;
}
return false;
};
update();
vr.launch();
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
const auto & tictoc = []()
{
static double t_start = igl::get_seconds();
double diff = igl::get_seconds()-t_start;
t_start += diff;
return diff;
};
// Create an interesting shape with sharp features using SDF CSG with spheres.
const auto & sphere = [](
const Eigen::RowVector3d & c,
const double r,
const Eigen::RowVector3d & x)->double
{
return (x-c).norm() - r;
};
const std::function<double(const Eigen::RowVector3d & x)> f =
[&](const Eigen::RowVector3d & x)->double
{
return
std::min(
std::min(std::max(
sphere(Eigen::RowVector3d(-0.2,0,-0.2),0.5,x),
-sphere(Eigen::RowVector3d(+0.2,0,0.2),0.5,x)),
sphere(Eigen::RowVector3d(-0.15,0,-0.15),0.3,x)
),
std::max(
std::max(
sphere(Eigen::RowVector3d(-0.2,-0.5,-0.2),0.6,x),x(1)+0.45),-0.6-x(1))
);
};
Eigen::RowVector3d p0(-0.2,0.5,-0.2);
assert(abs(f(p0)) < 1e-10 && "p0 should be on zero level-set");
// Simple finite difference gradients
const auto & fd = [](
const std::function<double(const Eigen::RowVector3d&)> & f,
const Eigen::RowVector3d & x)
{
const double eps = 1e-10;
Eigen::RowVector3d g;
for(int c = 0;c<3;c++)
{
const Eigen::RowVector3d xp = x+eps*Eigen::RowVector3d(c==0,c==1,c==2);
const double fp = f(xp);
const Eigen::RowVector3d xn = x-eps*Eigen::RowVector3d(c==0,c==1,c==2);
const double fn = f(xn);
g(c) = (fp-fn)/(2*eps);
}
return g;
};
const auto & f_grad = [&fd,&f](const Eigen::RowVector3d & x)
{
return fd(f,x).normalized();
};
Eigen::MatrixXd V;
Eigen::MatrixXi Q,F;
Eigen::MatrixXd mcV,mcN;
Eigen::MatrixXi mcF;
// Grid parameters
const Eigen::RowVector3d min_corner(-2,-2,-2);
const Eigen::RowVector3d max_corner(+2,+2,+2);
const int s = 256;
int nx = s+1;
int ny = s+1;
int nz = s+1;
const Eigen::RowVector3d step =
(max_corner-min_corner).array()/(Eigen::RowVector3d(nx,ny,nz).array()-1);
// Sparse grid below assumes regular grid
assert((step(0) == step(1))&&(step(0) == step(2)));
// Dual contouring parameters
bool constrained = false;
bool triangles = false;
bool root_finding = true;
for(int pass = 0;pass<2;pass++)
{
const bool sparse = pass == 1;
printf("Using %s grid..\n",sparse?"sparse":"dense");
if(sparse)
{
// igl::sparse_voxel_grid assumes (0,0,0) lies on the grid. But dense igl::grid
// below won't necessarily do that depending on nx,ny,nz.
tictoc();
Eigen::MatrixXd GV;
Eigen::VectorXd Gf;
Eigen::Matrix<int,Eigen::Dynamic,8> GI;
igl::sparse_voxel_grid(p0,f,step(0),16.*pow(step(0),-2.),Gf,GV,GI);
const auto t_Gf = tictoc();
printf(" %5f secs to populate sparse grid of %ld cells\n",t_Gf+tictoc(),GI.rows());
// Dual contouring requires list of sparse edges (not cells)
// extract _all_ edges from sparse_voxel_grid (conservative)
Eigen::Matrix<int,Eigen::Dynamic,2> GI2;
{
Eigen::Matrix<int,Eigen::Dynamic,2> all_GI2(GI.rows()*12,2);
all_GI2 <<
// front
GI.col(0),GI.col(1),
GI.col(1),GI.col(2),
GI.col(2),GI.col(3),
GI.col(3),GI.col(0),
// back
GI.col(4+0),GI.col(4+1),
GI.col(4+1),GI.col(4+2),
GI.col(4+2),GI.col(4+3),
GI.col(4+3),GI.col(4+0),
// sides
GI.col(0),GI.col(4+0),
GI.col(1),GI.col(4+1),
GI.col(2),GI.col(4+2),
GI.col(3),GI.col(4+3);
Eigen::VectorXi _1,_2;
igl::unique_simplices(all_GI2,GI2,_1,_2);
}
tictoc();
Eigen::RowVector3d step =
(max_corner-min_corner).array()/(Eigen::RowVector3d(nx,ny,nz).array()-1);
igl::dual_contouring(
f,f_grad,step,Gf,GV,GI2,constrained,triangles,root_finding,V,Q);
printf(" %5f secs dual contouring\n",t_Gf+tictoc());
// Could use igl::marching_cubes once
// https://github.com/libigl/libigl/pull/1687 is merged
tictoc();
igl::copyleft::marching_cubes(Gf,GV,GI,mcV, mcF);
printf(" %5f secs marching cubes\n",t_Gf+tictoc());
}else
{
tictoc();
igl::dual_contouring(
f,f_grad,min_corner,max_corner,nx,ny,nz,constrained,triangles,root_finding,V,Q);
printf(" %5f secs dual contouring\n",tictoc());
// build and sample grid
tictoc();
Eigen::MatrixXd GV;
igl::grid(Eigen::RowVector3i(nx,ny,nz),GV);
Eigen::VectorXd Gf(GV.rows());
igl::parallel_for(GV.rows(),[&](const int i)
{
GV.row(i).array() *= (max_corner-min_corner).array();
GV.row(i) += min_corner;
Gf(i) = f(GV.row(i));
},1000ul);
const auto t_grid = tictoc();
igl::marching_cubes(Gf,GV,nx,ny,nz,0,mcV,mcF);
const auto t_mc = tictoc();
printf(" %5f secs (%5f + %5f) marching cubes\n",t_grid+t_mc,t_grid,t_mc);
}
}
// Crisp (as possible) rendering of resulting MC triangle mesh
igl::per_corner_normals(mcV,mcF,20,mcN);
// Crisp rendering of resulting DC quad mesh with edges
Eigen::MatrixXi E;
Eigen::MatrixXd VV,N,NN;
Eigen::VectorXi J;
Eigen::MatrixXi FF;
if(triangles)
{
VV = V;
FF = Q;
E.resize(Q.rows()*3,2);
E<<
Q.col(0), Q.col(1),
Q.col(1), Q.col(2),
Q.col(2), Q.col(0);
}else
{
Eigen::VectorXi I,C;
igl::polygon_corners(Q,I,C);
E.resize(Q.rows()*4,2);
E<<
Q.col(0), Q.col(1),
Q.col(1), Q.col(2),
Q.col(2), Q.col(3),
Q.col(3), Q.col(0);
igl::per_face_normals(V,I,C,N,VV,FF,J);
igl::slice(N,J,1,NN);
igl::per_corner_normals(V,I,C,20,N,VV,FF,J,NN);
}
igl::opengl::glfw::Viewer vr;
bool show_edges = true;
bool use_dc = true;
const auto update = [&]()
{
const bool was_face_based = vr.data().face_based ;
vr.data().clear();
if(use_dc)
{
vr.data().set_mesh(VV,FF);
vr.data().show_lines = false;
vr.data().set_normals(NN);
if(show_edges)
{
vr.data().clear_edges();
vr.data().set_edges(V,E,Eigen::RowVector3d(0,0,0));
}
}else
{
vr.data().set_mesh(mcV,mcF);
vr.data().set_normals(mcN);
vr.data().show_lines = show_edges;
}
vr.data().face_based = was_face_based;
};
update();
vr.data().face_based = true;
vr.callback_key_pressed = [&](decltype(vr) &,unsigned int key, int mod)
{
switch(key)
{
case ' ': use_dc=!use_dc; update();return true;
case 'L': case 'l': show_edges=!show_edges; update();return true;
}
return false;
};
std::cout<<R"(
[space] Toggle between dual contouring and marching cubes
)";
vr.launch();
}
在上面的精确离散测地线距离示例中,将精确计算测地线距离。这是一项昂贵的操作: O(n²log(n)) 对于带有边缘的 n 网格。2013年,Crane等人 47 提出了一种方法,通过求解表面上的热方程,过滤结果,然后通过求解泊松方程重建平滑解,更快地计算近似测地线距离。该方法从瓦拉丹的观察开始,两点 x y 之间的测地线距离 d(x,y) 等于一段时间 y 后扩散 x 的热量对数的平方根 t :
热内核在哪里 kt,x 。我们可以将这种热扩散问题视为将热针放在上面 x ,然后在几秒钟后 t 测量点 y 的温度。
如果我们有足够的数值精度和精度,我们可以简单地评估 √(−4tlogu) 一个小的时间参数 t 。Crane等人观察到的问题是,我们对数值 u 的数值精度远远不够。然而,渐变的方向 ∇u 出奇地准确。因此,他们的想法是获取 的 u 梯度,归一化这些向量以获得梯度方向(单位向量)。然后求解泊松方程,将这些方向积分到实际距离值中。
此方法涉及反转 n×n 稀疏矩阵(运算 O(n1.⋯) ),但如果使用 Cholesky 分解,则因式分解是预计算,即使更改了测地线距离的来源,也可以重用。对于新源,只需执行反向替换。
在 libigl 中,您可以使用此方法通过两个步骤计算网格 ( V , F ) 从源顶点索引列表 gamma 到矢量 D 的近似测地线距离:
igl::HeatGeodesicsData<double> data;
igl::heat_geodesics_precompute(V,F,data);
...
igl::heat_geodesics_solve(data,gamma,D);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void set_colormap(igl::opengl::glfw::Viewer & viewer)
{
const int num_intervals = 30;
Eigen::MatrixXd CM(num_intervals,3);
// Colormap texture
for(int i = 0;i<num_intervals;i++)
{
double t = double(num_intervals - i - 1)/double(num_intervals-1);
CM(i,0) = std::max(std::min(2.0*t-0.0,1.0),0.0);
CM(i,1) = std::max(std::min(2.0*t-1.0,1.0),0.0);
CM(i,2) = std::max(std::min(6.0*t-5.0,1.0),0.0);
}
igl::isolines_map(Eigen::MatrixXd(CM),CM);
viewer.data().set_colormap(CM);
}
int main(int argc, char *argv[])
{
// Create the peak height field
Eigen::MatrixXi F;
Eigen::MatrixXd V;
igl::read_triangle_mesh( argc>1?argv[1]: TUTORIAL_SHARED_PATH "/beetle.off",V,F);
// Precomputation
igl::HeatGeodesicsData<double> data;
double t = std::pow(igl::avg_edge_length(V,F),2);
const auto precompute = [&]()
{
if(!igl::heat_geodesics_precompute(V,F,t,data))
{
std::cerr<<"Error: heat_geodesics_precompute failed."<<std::endl;
exit(EXIT_FAILURE);
};
};
precompute();
igl::opengl::glfw::Viewer viewer;
bool down_on_mesh = false;
const auto update = [&]()->bool
{
int fid;
Eigen::Vector3f bc;
// Cast a ray in the view direction starting from the mouse position
double x = viewer.current_mouse_x;
double y = viewer.core().viewport(3) - viewer.current_mouse_y;
if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core().view,
viewer.core().proj, viewer.core().viewport, V, F, fid, bc))
{
Eigen::VectorXd D;
// if big mesh, just use closest vertex. Otherwise, blend distances to
// vertices of face using barycentric coordinates.
if(F.rows()>100000)
{
// 3d position of hit
const Eigen::RowVector3d m3 =
V.row(F(fid,0))*bc(0) + V.row(F(fid,1))*bc(1) + V.row(F(fid,2))*bc(2);
int cid = 0;
Eigen::Vector3d(
(V.row(F(fid,0))-m3).squaredNorm(),
(V.row(F(fid,1))-m3).squaredNorm(),
(V.row(F(fid,2))-m3).squaredNorm()).minCoeff(&cid);
const int vid = F(fid,cid);
igl::heat_geodesics_solve(data,(Eigen::VectorXi(1,1)<<vid).finished(),D);
}else
{
D = Eigen::VectorXd::Zero(V.rows());
for(int cid = 0;cid<3;cid++)
{
const int vid = F(fid,cid);
Eigen::VectorXd Dc;
igl::heat_geodesics_solve(data,(Eigen::VectorXi(1,1)<<vid).finished(),Dc);
D += Dc*bc(cid);
}
}
viewer.data().set_data(D);
return true;
}
return false;
};
viewer.callback_mouse_down =
[&](igl::opengl::glfw::Viewer& viewer, int, int)->bool
{
if(update())
{
down_on_mesh = true;
return true;
}
return false;
};
viewer.callback_mouse_move =
[&](igl::opengl::glfw::Viewer& viewer, int, int)->bool
{
if(down_on_mesh)
{
update();
return true;
}
return false;
};
viewer.callback_mouse_up =
[&down_on_mesh](igl::opengl::glfw::Viewer& viewer, int, int)->bool
{
down_on_mesh = false;
return false;
};
std::cout<<R"(Usage:
[click] Click on shape to pick new geodesic distance source
,/. Decrease/increase t by factor of 10.0
D,d Toggle using intrinsic Delaunay discrete differential operators
)";
viewer.callback_key_pressed =
[&](igl::opengl::glfw::Viewer& /*viewer*/, unsigned int key, int mod)->bool
{
switch(key)
{
default:
return false;
case 'D':
case 'd':
data.use_intrinsic_delaunay = !data.use_intrinsic_delaunay;
std::cout<<(data.use_intrinsic_delaunay?"":"not ")<<
"using intrinsic delaunay..."<<std::endl;
precompute();
update();
break;
case '.':
case ',':
t *= (key=='.'?10.0:0.1);
precompute();
update();
std::cout<<"t: "<<t<<std::endl;
break;
}
return true;
};
// Show mesh
viewer.data().set_mesh(V, F);
viewer.data().set_data(Eigen::VectorXd::Zero(V.rows()));
set_colormap(viewer);
viewer.data().show_lines = false;
viewer.launch();
}
Eigen::MatrixXd l;
igl::edge_lengths(V,F,l);
Eigen::MatrixXd l_intrinsic;
Eigen::MatrixXi F_intrinsic;
igl::intrinsic_delaunay_triangulation(l,F,l_intrinsic,F_intrinsic);
Eigen::SparseMatrix<double> L;
igl::intrinsic_delaunay_cotmatrix(V,F,L);
igl::HeatGeodesicsData<double> data;
data.use_intrinsic_delaunay = true;
igl::heat_geodesics_precompute(V,F,data);
...
igl::heat_geodesics_solve(data,gamma,D);
2018年,Barill等人 50 演示了如何显著加快上述广义绕组数的计算速度。三角形网格的广义缠绕数的原始定义也扩展到(定向)点云。
igl::FastWindingNumberBVH fwn_bvh;
igl::fast_winding_number(V.cast<float>(),F,2,fwn_bvh);
Eigen::VectorXf W;
igl::fast_winding_number(fwn_bvh,2,Q.cast<float>(),W);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
const auto time = [](std::function<void(void)> func)->double
{
const double t_before = igl::get_seconds();
func();
const double t_after = igl::get_seconds();
return t_after-t_before;
};
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::read_triangle_mesh(argc>1?argv[1]:TUTORIAL_SHARED_PATH "/bunny.off",V,F);
// Sample mesh for point cloud
Eigen::MatrixXd P,N;
{
Eigen::VectorXi I;
Eigen::SparseMatrix<double> B;
igl::random_points_on_mesh(10000,V,F,B,I);
P = B*V;
Eigen::MatrixXd FN;
igl::per_face_normals(V,F,FN);
N.resize(P.rows(),3);
for(int p = 0;p<I.rows();p++)
{
N.row(p) = FN.row(I(p));
}
}
// Build octree
std::vector<std::vector<int > > O_PI;
Eigen::MatrixXi O_CH;
Eigen::MatrixXd O_CN;
Eigen::VectorXd O_W;
igl::octree(P,O_PI,O_CH,O_CN,O_W);
Eigen::VectorXd A;
{
Eigen::MatrixXi I;
igl::knn(P,20,O_PI,O_CH,O_CN,O_W,I);
// CGAL is only used to help get point areas
igl::copyleft::cgal::point_areas(P,I,N,A);
}
if(argc<=1)
{
// corrupt mesh
Eigen::MatrixXd BC;
igl::barycenter(V,F,BC);
Eigen::MatrixXd OV = V;
V.resize(F.rows()*3,3);
for(int f = 0;f<F.rows();f++)
{
for(int c = 0;c<3;c++)
{
int v = f+c*F.rows();
// random rotation about barycenter
Eigen::AngleAxisd R(
0.5*static_cast <double> (rand()) / static_cast <double> (RAND_MAX),
Eigen::Vector3d::Random(3,1));
V.row(v) = (OV.row(F(f,c))-BC.row(f))*R.matrix()+BC.row(f);
F(f,c) = v;
}
}
}
// Generate a list of random query points in the bounding box
Eigen::MatrixXd Q = Eigen::MatrixXd::Random(1000000,3);
const Eigen::RowVector3d Vmin = V.colwise().minCoeff();
const Eigen::RowVector3d Vmax = V.colwise().maxCoeff();
const Eigen::RowVector3d Vdiag = Vmax-Vmin;
for(int q = 0;q<Q.rows();q++)
{
Q.row(q) = (Q.row(q).array()*0.5+0.5)*Vdiag.array() + Vmin.array();
}
// Positions of points inside of point cloud P
Eigen::MatrixXd QiP;
{
Eigen::MatrixXd O_CM;
Eigen::VectorXd O_R;
Eigen::MatrixXd O_EC;
printf(" point cloud precomputation (% 8ld points): %g secs\n",
P.rows(),
time([&](){igl::fast_winding_number(P,N,A,O_PI,O_CH,2,O_CM,O_R,O_EC);}));
Eigen::VectorXd WiP;
printf(" point cloud evaluation (% 8ld queries): %g secs\n",
Q.rows(),
time([&](){igl::fast_winding_number(P,N,A,O_PI,O_CH,O_CM,O_R,O_EC,Q,2,WiP);}));
igl::slice_mask(Q,(WiP.array()>0.5).eval(),1,QiP);
}
// Positions of points inside of triangle soup (V,F)
Eigen::MatrixXd QiV;
{
igl::FastWindingNumberBVH fwn_bvh;
printf("triangle soup precomputation (% 8ld triangles): %g secs\n",
F.rows(),
time([&](){igl::fast_winding_number(V.cast<float>().eval(),F,2,fwn_bvh);}));
Eigen::VectorXf WiV;
printf(" triangle soup evaluation (% 8ld queries): %g secs\n",
Q.rows(),
time([&](){igl::fast_winding_number(fwn_bvh,2,Q.cast<float>().eval(),WiV);}));
igl::slice_mask(Q,WiV.array()>0.5,1,QiV);
}
// Visualization
igl::opengl::glfw::Viewer viewer;
// For dislpaying normals as little line segments
Eigen::MatrixXd PN(2*P.rows(),3);
Eigen::MatrixXi E(P.rows(),2);
const double bbd = igl::bounding_box_diagonal(V);
for(int p = 0;p<P.rows();p++)
{
E(p,0) = 2*p;
E(p,1) = 2*p+1;
PN.row(E(p,0)) = P.row(p);
PN.row(E(p,1)) = P.row(p)+bbd*0.01*N.row(p);
}
bool show_P = false;
int show_Q = 0;
int query_data = 0;
viewer.data_list[query_data].set_mesh(V,F);
viewer.data_list[query_data].clear();
viewer.data_list[query_data].point_size = 2;
viewer.append_mesh();
int object_data = 1;
viewer.data_list[object_data].set_mesh(V,F);
viewer.data_list[object_data].point_size = 5;
const auto update = [&]()
{
viewer.data_list[query_data].clear();
switch(show_Q)
{
case 1:
// show all Q
viewer.data_list[query_data].set_points(Q,Eigen::RowVector3d(0.996078,0.760784,0.760784));
break;
case 2:
// show all Q inside
if(show_P)
{
viewer.data_list[query_data].set_points(QiP,Eigen::RowVector3d(0.564706,0.847059,0.768627));
}else
{
viewer.data_list[query_data].set_points(QiV,Eigen::RowVector3d(0.564706,0.847059,0.768627));
}
break;
}
viewer.data_list[object_data].clear();
if(show_P)
{
viewer.data_list[object_data].set_points(P,Eigen::RowVector3d(1,1,1));
viewer.data_list[object_data].set_edges(PN,E,Eigen::RowVector3d(0.8,0.8,0.8));
}else
{
viewer.data_list[object_data].set_mesh(V,F);
}
};
viewer.callback_key_pressed =
[&](igl::opengl::glfw::Viewer &, unsigned int key, int mod)
{
switch(key)
{
default:
return false;
case '1':
show_P = !show_P;
break;
case '2':
show_Q = (show_Q+1) % 3;
break;
}
update();
return true;
};
std::cout<<R"(
FastWindingNumber
1 Toggle point cloud and triangle soup
2 Toggle hiding query points, showing query points, showing inside queries
)";
update();
viewer.launch();
}
// Build octree
std::vector<std::vector<int > > O_PI;
Eigen::MatrixXi O_CH;
Eigen::MatrixXd O_CN;
Eigen::VectorXd O_W;
igl::octree(P,O_PI,O_CH,O_CN,O_W);
Eigen::VectorXd A;
{
Eigen::MatrixXi I;
igl::knn(P,20,O_PI,O_CH,O_CN,O_W,I);
// CGAL is only used to help get point areas
igl::copyleft::cgal::point_areas(P,I,N,A);
}
Eigen::MatrixXd O_CM;
Eigen::VectorXd O_R;
Eigen::MatrixXd O_EC;
igl::fast_winding_number(P,N,A,O_PI,O_CH,2,O_CM,O_R,O_EC);
Eigen::VectorXd W;
igl::fast_winding_number(P,N,A,O_PI,O_CH,O_CM,O_R,O_EC,Q,2,W);
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2019 Alec Jacobson
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
Eigen::MatrixXd OVX,VX,VY;
Eigen::MatrixXi FX,FY;
igl::read_triangle_mesh( argc>1?argv[1]: TUTORIAL_SHARED_PATH "/decimated-max.obj",VY,FY);
const double bbd = (VY.colwise().maxCoeff()-VY.colwise().minCoeff()).norm();
FX = FY;
{
// sprinkle a noise so that we can see z-fighting when the match is perfect.
const double h = igl::avg_edge_length(VY,FY);
OVX = VY + 1e-2*h*Eigen::MatrixXd::Random(VY.rows(),VY.cols());
}
VX = OVX;
igl::AABB<Eigen::MatrixXd,3> Ytree;
Ytree.init(VY,FY);
Eigen::MatrixXd NY;
igl::per_face_normals(VY,FY,NY);
igl::opengl::glfw::Viewer v;
std::cout<<R"(
[space] conduct a single iterative closest point iteration
R,r reset to a random orientation and offset
)";
const auto apply_random_rotation = [&]()
{
const Eigen::Matrix3d R = Eigen::AngleAxisd(
2.*igl::PI*(double)rand()/RAND_MAX*0.3, igl::random_dir()).matrix();
const Eigen::RowVector3d cen =
0.5*(VY.colwise().maxCoeff()+VY.colwise().minCoeff());
VX = ((OVX*R).rowwise()+(cen-cen*R)).eval();
};
const auto single_iteration = [&]()
{
// Perform single iteration of ICP method
Eigen::Matrix3d R;
Eigen::RowVector3d t;
igl::iterative_closest_point(VX,FX,VY,FY,Ytree,NY,1000,1,R,t);
VX = ((VX*R).rowwise()+t).eval();
v.data().set_mesh(VX,FX);
v.data().compute_normals();
};
v.callback_pre_draw = [&](igl::opengl::glfw::Viewer &)->bool
{
if(v.core().is_animating)
{
single_iteration();
}
return false;
};
v.callback_key_pressed =
[&](igl::opengl::glfw::Viewer &,unsigned char key,int)->bool
{
switch(key)
{
case ' ':
{
v.core().is_animating = false;
single_iteration();
return true;
}
case 'R':
case 'r':
// Random rigid transformation
apply_random_rotation();
v.data().set_mesh(VX,FX);
v.data().compute_normals();
return true;
break;
}
return false;
};
v.data().set_mesh(VY,FY);
v.data().set_colors(Eigen::RowVector3d(1,1,1));
v.data().show_lines = false;
v.append_mesh();
v.data().set_mesh(VX,FX);
v.data().show_lines = false;
v.launch();
}
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2020 Alec Jacobson
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
Eigen::MatrixXd V;
Eigen::MatrixXi T,F;
igl::readMESH(argc>1?argv[1]: TUTORIAL_SHARED_PATH "/octopus-low.mesh",V,T,F);
// Some per-tet data
Eigen::VectorXd D;
{
Eigen::MatrixXd BC;
igl::barycenter(V,T,BC);
Eigen::VectorXd vol;
igl::volume(V,T,vol);
const Eigen::RowVectorXd c = vol.transpose()*BC/vol.array().sum();
D = (BC.rowwise()-c).rowwise().norm();
}
// Plot the mesh
igl::opengl::glfw::Viewer viewer;
double t = 1;
double s = 1;
const auto update = [&]()
{
Eigen::MatrixXd EV;
Eigen::MatrixXi EF;
Eigen::VectorXi I,J;
igl::exploded_view(V,T,s,t,EV,EF,I,J);
Eigen::VectorXd DJ;
igl::slice(D,J,1,DJ);
static bool first = true;
if(first)
{
viewer.data().clear();
viewer.data().set_mesh(EV,EF);
first = false;
}else
{
viewer.data().set_vertices(EV);
}
viewer.data().set_face_based(true);
Eigen::MatrixXd C;
igl::colormap(igl::COLOR_MAP_TYPE_VIRIDIS,DJ,true,C);
viewer.data().set_colors(C);
};
int mod = 0;
float prev_y;
viewer.callback_mouse_move = [&](igl::opengl::glfw::Viewer &, int x, int y)->bool
{
if((mod & IGL_MOD_SHIFT)||(mod & IGL_MOD_ALT))
{
if(mod & IGL_MOD_SHIFT)
{
t = std::min(std::max(t+0.001*(y-prev_y),1.),2.);
}
if(mod & IGL_MOD_ALT)
{
s = std::min(std::max(s+0.001*(y-prev_y),0.),1.);
}
prev_y = y;
update();
return true;
}
return false;
};
// get modifier
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer &, unsigned char key, int _mod)->bool
{
prev_y = viewer.current_mouse_y;
mod = _mod;
return false;
};
viewer.callback_key_up =
[&](igl::opengl::glfw::Viewer &, unsigned char key, int _mod)->bool
{
mod = _mod;
return false;
};
update();
viewer.launch();
}
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2020 Alec Jacobson
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::read_triangle_mesh(
argc>1?argv[1]: TUTORIAL_SHARED_PATH "/elephant.obj",V,F);
Eigen::MatrixXd N;
igl::per_vertex_normals(V,F,N);
const double bbd = (V.colwise().maxCoeff()- V.colwise().minCoeff()).norm();
Eigen::MatrixXd P_blue;
Eigen::MatrixXd N_blue;
Eigen::VectorXd A_blue;
Eigen::MatrixXd C_blue;
{
const int n_desired = argc>2?atoi(argv[2]):50000;
// Heuristic to determine radius from desired number
const double r = [&V,&F](const int n)
{
Eigen::VectorXd A;
igl::doublearea(V,F,A);
return sqrt(((A.sum()*0.5/(n*0.6162910373))/igl::PI));
}(n_desired);
printf("blue noise radius: %g\n",r);
Eigen::MatrixXd B;
Eigen::VectorXi I;
igl::blue_noise(V,F,r,B,I,P_blue);
igl::barycentric_interpolation(N,F,B,I,N_blue);
N_blue.rowwise().normalize();
}
Eigen::MatrixXd P_white;
Eigen::MatrixXd N_white;
Eigen::VectorXd A_white;
Eigen::MatrixXd C_white;
{
Eigen::MatrixXd B;
Eigen::VectorXi I;
igl::random_points_on_mesh(P_blue.rows(),V,F,B,I,P_white);
igl::barycentric_interpolation(N,F,B,I,N_white);
N_white.rowwise().normalize();
}
// Color
Eigen::RowVector3d C(1,1,1);
// Plot the mesh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
viewer.data().show_lines = false;
viewer.data().show_faces = false;
viewer.data().point_size = 4;
bool use_blue = true;
const auto update = [&]()
{
const Eigen::RowVector3d orange(1.0,0.7,0.2);
if(use_blue)
{
viewer.data().set_points(P_blue,Eigen::RowVector3d(0.3,0.4,1.0));
viewer.data().set_edges_from_vector_field(P_blue,bbd*0.01*N_blue,orange);
}else
{
viewer.data().set_points(P_white,Eigen::RowVector3d(1,1,1));
viewer.data().set_edges_from_vector_field(P_white,bbd*0.01*N_white,orange);
}
};
update();
viewer.callback_key_pressed =
[&](igl::opengl::glfw::Viewer &,unsigned char key,int)->bool
{
switch(key)
{
case ' ':
{
use_blue = !use_blue;
update();
return true;
}
}
return false;
};
std::cout<<R"(
[space] toggle between blue and white noise samplings
)";
viewer.launch();
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
typedef Eigen::SparseMatrix<double> SparseMat;
//Constants used for smoothing
const double howMuchToSmoothBy = 1e-1;
const int howManySmoothingInterations = 50;
//Read our mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;
if(!igl::read_triangle_mesh
(argc>1?argv[1]: TUTORIAL_SHARED_PATH "/elephant.obj",V,F)) {
std::cout << "Failed to load mesh." << std::endl;
}
//Orient edges for plotting
Eigen::MatrixXi E, oE;
igl::orient_halfedges(F, E, oE);
//Compute edge midpoints & edge vectors
Eigen::MatrixXd edgeMps, parVec, perpVec;
igl::edge_midpoints(V, F, E, oE, edgeMps);
igl::edge_vectors(V, F, E, oE, parVec, perpVec);
//Constructing a function to add noise to
const auto zraw_function = [] (const Eigen::Vector3d& x) {
return Eigen::Vector3d(0.2*x(1) + cos(2*x(1)+0.2),
0.5*x(0) + 0.15,
0.3*cos(0.2+igl::PI*x(2)));
};
Eigen::VectorXd zraw(2*edgeMps.rows());
for(int i=0; i<edgeMps.rows(); ++i) {
const Eigen::Vector3d f = zraw_function(edgeMps.row(i));
zraw(i) = f.dot(parVec.row(i));
zraw(i+edgeMps.rows()) = f.dot(perpVec.row(i));
}
//Add noise
srand(71);
const double l = 15;
Eigen::VectorXd znoisy = zraw + l*Eigen::VectorXd::Random(zraw.size());
//Denoise function using the vector Dirichlet energy
Eigen::VectorXd zsmoothed = znoisy;
for(int i=0; i<howManySmoothingInterations; ++i) {
//Compute Laplacian and mass matrix
SparseMat L, M;
igl::cr_vector_mass(V, F, E, oE, M);
igl::cr_vector_laplacian(V, F, E, oE, L);
//Implicit step
Eigen::SimplicialLDLT<SparseMat> rhsSolver(M + howMuchToSmoothBy*L);
zsmoothed = rhsSolver.solve(M*zsmoothed);
}
//Convert vector fields for plotting
const auto cr_result_to_vecs_and_colors = [&]
(const Eigen::VectorXd& z, Eigen::MatrixXd& vecs, Eigen::MatrixXd& colors) {
vecs.resize(edgeMps.rows(), 3);
for(int i=0; i<edgeMps.rows(); ++i) {
vecs.row(i) = z(i)*parVec.row(i)
+ z(i+edgeMps.rows())*perpVec.row(i);
}
igl::average_from_edges_onto_vertices
(F, E, oE, vecs.rowwise().norm(), colors);
};
Eigen::MatrixXd noisyvecs, noisycolors, smoothedvecs, smoothedcolors,
rawvecs, rawcolors;
cr_result_to_vecs_and_colors(znoisy, noisyvecs, noisycolors);
cr_result_to_vecs_and_colors(zsmoothed, smoothedvecs, smoothedcolors);
cr_result_to_vecs_and_colors(zraw, rawvecs, rawcolors);
//Viewer that shows noisy and denoised functions
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
viewer.data().show_lines = false;
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer & viewer, unsigned char key, int mod)->bool
{
const Eigen::MatrixXd *vecs, *colors;
switch(key) {
case '1':
vecs = &rawvecs;
colors = &rawcolors;
break;
case '2':
vecs = &noisyvecs;
colors = &noisycolors;
break;
case '3':
vecs = &smoothedvecs;
colors = &smoothedcolors;
break;
default:
return false;
}
viewer.data().set_data(*colors);
viewer.data().clear_edges();
const double s = 0.08; //How much to scale vectors during plotting
viewer.data().add_edges(edgeMps, edgeMps + s*(*vecs),
Eigen::RowVector3d(0.1, 0.1, 0.1));
return true;
};
std::cout << R"(Usage:
1 Show raw function
2 Show noisy function
3 Show smoothed function
)";
Eigen::MatrixXd CM;
igl::parula(Eigen::VectorXd::LinSpaced
(500,znoisy.minCoeff(), znoisy.maxCoeff()).eval(), true, CM);
viewer.data().set_colormap(CM);
viewer.callback_key_down(viewer, '1', 0);
viewer.launch();
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
typedef Eigen::SparseMatrix<double> SparseMat;
typedef Eigen::Matrix<double, 1, 1> Vector1d;
typedef Eigen::Matrix<int, 1, 1> Vector1i;
//Constants used for smoothing
const double howMuchToSmoothBy = 1e-1;
const int howManySmoothingInterations = 50;
//Read our mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;
if(!igl::read_triangle_mesh
(argc>1?argv[1]: TUTORIAL_SHARED_PATH "/cheburashka.off",V,F)) {
std::cout << "Failed to load mesh." << std::endl;
}
//Compute vector Laplacian and mass matrix
Eigen::MatrixXi E, oE;//Compute Laplacian and mass matrix
SparseMat vecL, vecM;
igl::cr_vector_mass(V, F, E, oE, vecM);
igl::cr_vector_laplacian(V, F, E, oE, vecL);
const int m = vecL.rows()/2; //The number of edges in the mesh
//Convert the E / oE matrix format to list of edges / EMAP format required
// by the functions constructing scalar Crouzeix-Raviart functions
Eigen::MatrixXi Elist(m,2), EMAP(3*F.rows(),1);
for(int i=0; i<F.rows(); ++i) {
for(int j=0; j<3; ++j) {
const int e = E(i,j);
EMAP(i+j*F.rows()) = e;
if(oE(i,j)>0) {
Elist.row(e) << F(i, (j+1)%3), F(i, (j+2)%3);
}
}
}
SparseMat scalarL, scalarM;
igl::crouzeix_raviart_massmatrix(V, F, Elist, EMAP, scalarM);
igl::crouzeix_raviart_cotmatrix(V, F, Elist, EMAP, scalarL);
//Compute edge midpoints & edge vectors
Eigen::MatrixXd edgeMps, parVec, perpVec;
igl::edge_midpoints(V, F, E, oE, edgeMps);
igl::edge_vectors(V, F, E, oE, parVec, perpVec);
//Perform the vector heat method
const int initialIndex = 14319;
const double initialPara=0.95, initialPerp=0.08;
const double t = 0.01;
SparseMat Aeq;
Eigen::VectorXd Beq;
Eigen::VectorXi known = Eigen::Vector2i(initialIndex, initialIndex+m);
Eigen::VectorXd knownVals = Eigen::Vector2d(initialPara, initialPerp);
Eigen::VectorXd Y0 = Eigen::VectorXd::Zero(2*m), Yt;
Y0(initialIndex) = initialPara; Y0(initialIndex+m) = initialPerp;
igl::min_quad_with_fixed
(SparseMat(vecM+t*vecL), Eigen::VectorXd(-vecM*Y0), known, knownVals,
Aeq, Beq, false, Yt);
Eigen::VectorXd u0 = Eigen::VectorXd::Zero(m), ut;
u0(initialIndex) = sqrt(initialPara*initialPara + initialPerp*initialPerp);
Eigen::VectorXi knownScal = Vector1i(initialIndex);
Eigen::VectorXd knownScalVals = Vector1d(u0(initialIndex));
igl::min_quad_with_fixed
(SparseMat(scalarM+t*scalarL), Eigen::VectorXd(-scalarM*u0), knownScal,
knownScalVals, Aeq, Beq, false, ut);
Eigen::VectorXd phi0 = Eigen::VectorXd::Zero(m), phit;
phi0(initialIndex) = 1;
Eigen::VectorXd knownScalValsPhi = Vector1d(1);
igl::min_quad_with_fixed
(SparseMat(scalarM+t*scalarL), Eigen::VectorXd(-scalarM*phi0), knownScal,
knownScalValsPhi, Aeq, Beq, false, phit);
Eigen::ArrayXd Xtfactor = ut.array() /
(phit.array() * (Yt.array().segment(0,m)*Yt.array().segment(0,m)
+ Yt.array().segment(m,m)*Yt.array().segment(m,m)).sqrt());
Eigen::VectorXd Xt(2*m);
Xt.segment(0,m) = Xtfactor * Yt.segment(0,m).array();
Xt.segment(m,m) = Xtfactor * Yt.segment(m,m).array();
//Compute scalar heat colors
igl::HeatGeodesicsData<double> hgData;
igl::heat_geodesics_precompute(V, F, hgData);
Eigen::VectorXd heatColor;
Eigen::VectorXi gamma = Elist.row(initialIndex);
igl::heat_geodesics_solve(hgData, gamma, heatColor);
//Convert vector field for plotting
Eigen::MatrixXd vecs(m, 3);
for(int i=0; i<edgeMps.rows(); ++i) {
vecs.row(i) = Xt(i)*parVec.row(i) + Xt(i+edgeMps.rows())*perpVec.row(i);
}
//Viewer that shows parallel transported vector
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
viewer.data().show_lines = false;
viewer.data().set_data(heatColor.maxCoeff()-heatColor.array(), //invert colormap
igl::COLOR_MAP_TYPE_VIRIDIS);
const double s = 0.012; //How much to scale vectors during plotting
Eigen::MatrixXd vecColors(m, 3);
for(int i=0; i<m; ++i) {
vecColors.row(i) << 0.1, 0.1, 0.1;
}
vecColors.row(initialIndex) << 0.9, 0.1, 0.1;
viewer.data().add_edges(edgeMps, edgeMps + s*vecs, vecColors);
std::cout << R"(The red vector is parallel transported to every point on the surface.
The surface is shaded by geodesic distance from the red vector.
)"
<< std::endl;
viewer.launch();
return 0;
}