【libigl】有符号距离

7.04 有符号距离

3807c3d1c67a349784852727fd57214a.png

d99cc973d68b907a93507ee8bdcc7473.png

1b82433a402470d964cbcb600ee8e802.gif

这段代码中使用了libigl库和Eigen库,主要功能是提供一个图形界面来展示一个3D体网格,并允许用户通过键盘输入来修改视图,例如切换显示表面,修改透视平面的位置,以及计算点到网格的距离。以下是源代码中的类和方法的详细总结:

全局变量定义:

  • Eigen::MatrixXd V;:存储网格的顶点。

  • Eigen::MatrixXi T, F;:分别存储网格的四面体索引和面索引。

  • igl::AABB<Eigen::MatrixXd, 3> tree;:用于计算有符号距离的轴对齐包围盒(AABB)树。

  • igl::FastWindingNumberBVH fwn_bvh;:快速缠绕数BVH树,用于快速检测点在网格内外。

  • Eigen::MatrixXd FN, VN, EN;:分别存储网格面、顶点和边的法线。

  • Eigen::MatrixXi E;:存储边的索引。

  • Eigen::VectorXi EMAP;:存储边到其多面体的映射。

  • double max_distance = 1;:网格中最远点的距离。

  • double slice_z = 0.5;:切片高度比例,默认为0.5。

  • bool overlay = false;:是否覆盖显示网格表面。

  • bool useFastWindingNumber = false;:是否使用快速缠绕数进行距离的有符号测试。

  • const Eigen::MatrixXd CM:定义了50个RGB颜色的矩阵,用于数据可视化。

函数 void update_visualization(igl::opengl::glfw::Viewer & viewer):

  • 根据切片位置和可选的显示覆盖选项,更新视图中显示的网格。

函数 bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int mod):

  • 定义了键盘按下事件处理函数。根据用户输入的键盘按键,切换显示覆盖选项、调整切片高度、以及切换使用快速缠绕数或伪法线进行有符号距离计算。

主函数 int main(int argc, char *argv[]):

  • 定义了启动GUI的主程序。加载并初始化网格,设置Fast Winding Number和Pseudo Normal所需的数据结构,以及通过调用 igl::opengl::glfw::Viewer 来创建和启动视图。

总体来说,这段代码通过libigl和OpenGL提供了一个交互式的3D体网格视图和编辑环境,可以用于科研可视化或者教育演示。

// 包含igl库和Eigen库中的一系列函数
#include <igl/cat.h>
#include <igl/edge_lengths.h>
#include <igl/per_edge_normals.h>
#include <igl/per_face_normals.h>
#include <igl/per_vertex_normals.h>
#include <igl/point_mesh_squared_distance.h>
#include <igl/readMESH.h>
#include <igl/signed_distance.h>
#include <igl/slice_mask.h>
#include <igl/marching_tets.h>
#include <igl/upsample.h>
#include <igl/opengl/glfw/Viewer.h>
#include <igl/writeOBJ.h>
#include <Eigen/Sparse>
#include <iostream>


// 初始化矩阵来存储顶点和面片信息
Eigen::MatrixXd V;
Eigen::MatrixXi T,F;


// 初始化AABB树和快速绕组数(FWN)BVH
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;


// 切片的Z轴位置,默认为0.5
double slice_z = 0.5;
// 是否叠加显示模型表面
bool overlay = false;


// 是否使用快速绕组数来计算
bool useFastWindingNumber = false;


// 定义颜色映射矩阵
const Eigen::MatrixXd CM = 
  /* 此处省略具体数字内容,CM为一个50x3的矩阵,用于颜色映射 */
   (Eigen::MatrixXd(50,3)<<
  242,242,242,
  247,251,253,
  228,234,238,
  233,243,249,
  214,227,234,
  217,234,244,
  199,218,230,
  203,226,240,
  186,211,226,
  187,217,236,
  171,203,222,
  173,209,232,
  157,195,218,
  158,201,228,
  142,187,214,
  143,193,223,
  129,179,210,
  128,185,219,
  114,171,206,
  112,176,215,
  100,163,202,
  98,168,211,
  86,156,198,
  82,159,207,
  71,148,194,
  255,247,223,
  242,230,204,
  255,235,206,
  242,219,189,
  255,225,191,
  242,209,175,
  255,214,176,
  242,198,159,
  255,203,160,
  242,188,145,
  255,192,145,
  242,177,129,
  255,181,128,
  242,167,115,
  255,170,113,
  242,157,101,
  255,159,97,
  242,146,85,
  255,148,82,
  242,136,71,
  255,137,65,
  242,125,55,
  255,126,50,
  242,115,41,
  255,116,36).finished()/255.0;


// 更新视图器中的视觉效果的函数
void update_visualization(igl::opengl::glfw::Viewer & viewer)
{
  // 使用Eigen命名空间
  using namespace Eigen;
  using namespace std;
  
  // 定义切片平面, 平面的法线方向为z轴,根据slice_z计算平面位置
  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;
  
  // 从体网格中提取通过切片平面的三角网格,并细化面状况比较差的三角形
  {
    // J 变量用来存储行列式索引
    VectorXi J;
    // bary变量用于存储重心坐标
    SparseMatrix<double> bary;
    {
      // 在所有顶点上计算平面隐式函数的值
      const VectorXd IV = 
        (V.col(0)*plane(0) + 
         V.col(1)*plane(1) + 
         V.col(2)*plane(2)).array()
        + plane(3);
      
      // 使用marching tets算法提取交于该平面的网格
      igl::marching_tets(V,T,IV,V_vis,F_vis,J,bary);
      // 将提取的网格保存为OBJ文件
      igl::writeOBJ("vis.obj",V_vis,F_vis);
    }
    // 循环细化网格,直到所有边长都小于阈值
    while(true)
    {
      // l用来存储各边边长
      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);
    }
  }


  // 计算有符号距离
  VectorXd S_vis;


  // 如果没有使用快速绕组数计算有符号距离,我们使用伪法线来进行
  if (!useFastWindingNumber)
  {
    // I,N,C是中间变量
    VectorXi I;
    MatrixXd N,C;
    // 由于兔子模型是一个封闭的网格,所以采用伪法线来进行有符号的距离计算
    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);
  }    


  // 用于添加网格的lambda函数
  const auto & append_mesh = [&F_vis,&V_vis](
    const Eigen::MatrixXd & V,
    const Eigen::MatrixXi & F,
    const RowVector3d & color)
  {
    // 为F_vis矩阵添加新面,为V_vis矩阵添加新顶点
    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中的数据,并设置新的网格,颜色映射和数据
  viewer.data().clear();
  viewer.data().set_mesh(V_vis,F_vis);
  viewer.data().set_colormap(CM);
  viewer.data().set_data(S_vis);
  // 根据overlay变量来设置viewer的光照系数
  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变量的状态
      overlay ^= true;
      break;
    case '.':
      // 按'.'键来将切片的深度加0.01,但不超过0.99
      slice_z = std::min(slice_z+0.01,0.99);
      break;
    case ',':
      // 按','键来将切片的深度减0.01,但不少于0.01
      slice_z = std::max(slice_z-0.01,0.01);
      break;
    case '1':
      // 按'1'键使用快速绕组数来计算有符号距离
      useFastWindingNumber = true;
      break;
    case '2':
      // 按'2'键使用伪法线来计算有符号距离
      useFastWindingNumber = false;
      break;
  }
  // 更新视图器中的视觉效果
  update_visualization(viewer);
  return true;
}


// 主函数
int main(int argc, char *argv[])
{
  // 此处省略代码,主要是装载模型和初始化viewer
  igl::opengl::glfw::Viewer viewer;
  update_visualization(viewer);
  viewer.callback_key_down = &key_down;
  viewer.data().show_lines = false;
  viewer.launch();
}

上述代码是一个使用libigl库和Eigen库的复杂示例,它完成了3D体网格内部的等值面提取(marching tetrahedra),并可视化结果。代码的执行流畅为:

  1. 加载.mesh格式的3D体网格。

  2. 初始化AABB树和快速绕组数(Fast Winding Number)BVH。

  3. 根据网格和切片水平位置,生成切片网格。

  4. 对切片网格计算有无符号距离。

  5. 更新igl::opengl::glfw::Viewer中显示的网格,颜色和数据。

  6. 用户通过键盘操作如空格键、逗号和句号来改变可视化设置,'1' 和 '2' 来切换签名方法。

代码使用igl::maching_tets提取体网格中与切片平面相交的等值面,然后使用igl::upsample对不整齐的三角形进行细分,直至满足所需的精度。通过调整slice_z变量,用户可以在3D网格中移动切片平面位置。

为了展示切片等值面上有符号距离的可视化效果,代码计算在等值面网格顶点上的有符号距离,并将其映射到颜色条的颜色上。用户可以通过按键'1'和'2'在快速绕组数和伪法线方法之间切换,计算有符号距离。此外,按空格键可切换是否叠加显示原始网格表面。

The End

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值