【libigl】Libigl_Tutorials -第一章(共14小节)

6ede5e3b615002648039364c60c6b439.jpeg

第一章

我们通过一系列独立的示例介绍libigl。每个示例的目的是展示libigl的一个特性,同时应用于几何处理中的实际问题。在本章中,我们将介绍libigl的基本概念,并介绍一个简单的网格查看器,可以用于可视化表面网格及其属性。所有教程示例都是跨平台的,可以在MacOSX、Linux和Windows上编译。

Libigl设计原则在进入示例之前,我们总结一下libigl的主要设计原则:

没有复杂的数据类型。我们主要使用矩阵和向量。这极大地促进了代码的可重用性,并迫使函数作者暴露算法中使用的所有参数。

最小依赖性。我们只在必要时使用外部库,并将它们封装在一小组函数中。

头文件。使用我们的库非常简单,因为只需在项目中添加一个额外的包含目录即可。(如果您担心编译速度,也可以将库构建为静态库)

函数封装。每个函数(包括其完整实现)都包含在一对具有相同名称的.h/.cpp文件中。

许多Linux发行版在默认安装中不包括gcc和基本的开发工具。在Ubuntu上,您需要安装以下软件包:

sudo apt-get install \
  git \
  build-essential \
  cmake \
  libx11-dev \
  mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev \
  libxrandr-dev \
  libxi-dev \
  libxmu-dev \
  libblas-dev \
  libxinerama-dev \
  libxcursor-dev

下载Libigl您可以从我们的GitHub存储库下载libigl,也可以使用git进行克隆:

git clone https://github.com/libigl/libigl.git

核心libigl功能仅依赖于C++标准库和Eigen。在下面执行cmake时,可选依赖项将被下载。

要构建教程中的所有示例(和测试),您可以使用根文件夹中的CMakeLists.txt:

cd libigl/
mkdir build
cd build
cmake ../
make

一、FileIO

# 包含必要的头文件
#include <igl/readOFF.h> // 包含用于读取OFF文件格式的函数
#include <igl/writeOBJ.h> // 包含用于写入OBJ文件格式的函数
#include <iostream> // 包含标准输入输出流功能


// 定义Eigen库中的矩阵类型来存储顶点和面
Eigen::MatrixXd V; // V是存储网格顶点坐标的矩阵,MatrixXd表示动态大小的矩阵,其中元素类型为double
Eigen::MatrixXi F; // F是存储网格面顶点索引的矩阵,MatrixXi表示动态大小的矩阵,其中元素类型为int


// 主函数
int main(int argc, char *argv[])
{
  // 加载一个OFF格式的网格
  igl::readOFF(TUTORIAL_SHARED_PATH  "/cube.off", V, F); // 调用igl::readOFF函数读取文件


  // 打印顶点矩阵和面矩阵
  std::cout << "Vertices: " << std::endl << V << std::endl; // 输出"Vertices: ",然后输出顶点矩阵V
  std::cout << "Faces:    " << std::endl << F << std::endl; // 输出"Faces: ",然后输出面矩阵F


  // 将网格保存为OBJ格式
  igl::writeOBJ("cube.obj",V,F); // 调用igl::writeOBJ函数写入OBJ文件,文件名为"cube.obj"
}

以上代码的功能是加载一个OFF格式的三维网格模型,打印出该模型的顶点和面信息,然后将这个模型保存为OBJ格式的文件。这个过程通过使用Eigen库来处理矩阵运算,以及利用libigl库中的函数来实现对OFF和OBJ文件格式的读写操作。

二、DrawMesh

# 包含必要的头文件
#include <igl/readOFF.h> // 包含用于读取OFF文件格式的函数
#include <igl/opengl/glfw/Viewer.h> // 包含用于渲染和显示三维网格的查看器类


// 定义Eigen库中的矩阵类型来存储顶点和面
Eigen::MatrixXd V; // V是存储网格顶点坐标的矩阵,MatrixXd表示动态大小的矩阵,其中元素类型为double
Eigen::MatrixXi F; // F是存储网格面顶点索引的矩阵,MatrixXi表示动态大小的矩阵,其中元素类型为int


// 主函数
int main(int argc, char *argv[])
{
  // 加载一个OFF格式的网格
  igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F); // 调用igl::readOFF函数读取文件


  // 绘制网格
  igl::opengl::glfw::Viewer viewer; // 创建一个查看器实例
  viewer.data().set_mesh(V, F); // 使用set_mesh函数设置查看器中的网格数据
  viewer.launch(); // 启动查看器窗口,并渲染显示网格
}

以上代码的功能是加载一个OFF格式的三维网格模型(具体为“bunny.off”),然后创建一个查看器窗口来渲染并展示这个模型。代码中使用了Eigen库来存储模型的顶点和面数据,并利用libigl的查看器功能来进行三维网格的可视化展示。

三、Events

27a8032f17cba98a560a665abcbcb3ab.pngd2255e822f1f43c484b440cf4b4b1258.png

该代码扩展了之前的功能,现在加载了两个OFF格式网格。每个网格通过Eigen库的MatrixXd和MatrixXi来存储顶点和面数据。此外,代码提供了一个键盘回调函数key_down,允许在运行时通过按键 '1' 和 '2' 来在两个不同的网格模型之间切换显示。

回调函数key_down中使用了viewer.data().clear()来清除当前查看器中的网格数据,然后根据按下的键来选择加载不同的网格数据。函数viewer.data().set_mesh()用于设置新的顶点和面数据,函数viewer.core().align_camera_center()用于根据新的网格数据自动居中并对齐相机视角。

在main()函数中,使用igl::readOFF函数读取了两个网格文件,并在命令行中打印了用户指南。然后在启动查看器窗口之前,将键盘回调函数绑定到查看器,并初始化默认显示第一个网格模型。运行程序后,用户可以按键 '1' 和 '2' 来在“bumpy”(凹凸不平)网格和“fertility”(生育)网格之间切换。

// 包含必要的头文件
#include <igl/readOFF.h> // 包含用于读取OFF文件格式的函数
#include <igl/opengl/glfw/Viewer.h> // 包含用于渲染和显示三维网格的查看器类
#include <iostream> // 包含标准输入输出流功能


// 定义Eigen库中的矩阵类型来存储顶点和面
Eigen::MatrixXd V1,V2; // V1和V2分别存储两个网格顶点坐标的矩阵
Eigen::MatrixXi F1,F2; // F1和F2分别存储两个网格面顶点索引的矩阵


// 该函数在每次按键时被调用
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
{
  // 打印按下的键和对应的ASCII值
  std::cout<<"Key: "<<key<<" "<<(unsigned int)key<<std::endl;
  if (key == '1') // 检查是否按下了键'1'
  {
    // 在绘制网格前应调用clear函数
    viewer.data().clear();
    // 根据提供的顶点V和面F来创建或更新显示的网格
    // 如果已经显示了一个网格,则draw_mesh在V和F的大小与当前不同时返回错误
    viewer.data().set_mesh(V1, F1);
    // 使相机居中对齐于V1和F1定义的网格
    viewer.core().align_camera_center(V1,F1);
  }
  else if (key == '2') // 检查是否按下了键'2'
  {
    viewer.data().clear();
    // 设置第二个网格的顶点和面
    viewer.data().set_mesh(V2, F2);
    // 使相机居中对齐于V2和F2定义的网格
    viewer.core().align_camera_center(V2,F2);
  }


  return false;
}


// 主函数
int main(int argc, char *argv[])
{
  // 加载两个网格模型
  igl::readOFF(TUTORIAL_SHARED_PATH "/bumpy.off", V1, F1);
  igl::readOFF(TUTORIAL_SHARED_PATH "/fertility.off", V2, F2);
  // 打印使用说明
  std::cout << R"(
1 Switch to bump mesh
2 Switch to fertility mesh
  )";


  igl::opengl::glfw::Viewer viewer; // 创建查看器实例
  // 注册一个键盘回调函数,允许在两个加载的网格之间切换
  viewer.callback_key_down = &key_down;
  // 初始化查看器窗口显示的是第一个网格
  viewer.data().set_mesh(V1, F1);
  // 启动查看器窗口,并渲染显示网格
  viewer.launch();
}

四、Colors

c324ed2052de819e55de9528917c3891.png

以上代码的功能是加载一个OFF格式的三维网格模型(在本例中为“screwdriver.off”即螺丝刀模型),然后创建一个查看器窗口来渲染并展示这个模型。此外,该代码还使用顶点的位置信息来为每个顶点生成一个归一化的颜色,以根据其位置在模型上显示不同的颜色。

具体来说:

  • 调用 igl::readOFF 函数加载了名为 "screwdriver.off" 的螺丝刀模型。

  • 初始化 igl::opengl::glfw::Viewer 对象,并通过 set_mesh 方法设置查看器中渲染的网格数据。

  • 使用顶点坐标数据(V)经过归一化处理计算出每个顶点的颜色值(C)。

  • 调用 set_colors 方法将归一化后的顶点颜色(C)设置到查看器中,使渲染的三维网格拥有了顶点色彩。

  • 最后,调用 viewer.launch() 方法启动查看器窗口,用户可以在其中观察带有色彩的模型。

// 包含必要的头文件
#include <igl/readOFF.h> // 包含用于读取OFF文件格式的函数
#include <igl/opengl/glfw/Viewer.h> // 包含用于渲染和显示三维网格的查看器类


// 定义Eigen库中的矩阵类型来存储顶点、面和颜色
Eigen::MatrixXd V; // V是存储网格顶点坐标的矩阵
Eigen::MatrixXi F; // F是存储网格面顶点索引的矩阵
Eigen::MatrixXd C; // C是存储顶点颜色的矩阵


// 主函数
int main(int argc, char *argv[])
{
  // 加载一个OFF格式的网格
  igl::readOFF(TUTORIAL_SHARED_PATH "/screwdriver.off", V, F); // 使用igl::readOFF函数读取螺丝刀网格模型


  // 绘制网格
  igl::opengl::glfw::Viewer viewer; // 创建一个查看器实例
  viewer.data().set_mesh(V, F); // 设置查看器中的网格数据


  // 使用归一化后的顶点位置作为颜色
  C =
    (V.rowwise()            - V.colwise().minCoeff()).array().rowwise()/
    (V.colwise().maxCoeff() - V.colwise().minCoeff()).array();
  // 添加每个顶点的颜色
  viewer.data().set_colors(C); // 使用set_colors函数为网格顶点设置颜色


  // 启动查看器
  viewer.launch(); // 启动查看器窗口,并渲染显示网格
}

五、Overlays(windows系统配置有问题,linux OK)

ac1f8efeeeb8b89722a5f8ea07f25aa5.png

此代码主要实现了加载并可视化一个OFF格式的三维网格模型(本例中为“bunny.off”即兔子模型),同时找到并绘制了模型的包围盒。这包括计算包围盒的角点和边,以及将这些几何数据添加到查看器中以便可视化。

具体步骤:

使用 igl::readOFF 函数从指定路径加载了兔子模型的顶点坐标和面。

计算出了模型包围盒的两个对角顶点(m 和 M),并据此生成了包围盒的其他角点(V_box)以及边(E_box)。

利用 igl::opengl::glfw::Viewer 实例化查看器,并使用 set_mesh 方法来渲染模型。

通过 add_points 和 add_edges 方法添加了包围盒的角点和边到查看器,并分别用红色标记。

添加了两个顶点坐标作为标签,显示在包围盒的对角顶点处,并启用了自定义标签渲染。

使用 igl::opengl::glfw::imgui::ImGuiPlugin 实例化 ImGui 插件以使用 ImGui 功能来渲染和显示文本标签。

最后,调用 viewer.launch() 方法启动查看器窗口,渲染并展示了包含模型、包围盒及其标签的三维场景。

//包含必要的头文件
#include <igl/readOFF.h> // 包含用于读取OFF文件格式的函数
#include <igl/opengl/glfw/Viewer.h> // 包含用于渲染和显示三维网格的查看器类
#include <igl/opengl/glfw/imgui/ImGuiPlugin.h> // 包含libigl集成的ImGui插件相关功能,用于GUI渲染
#include <igl/opengl/glfw/imgui/ImGuiMenu.h> // 包含用于操作ImGui菜单的函数
#include <sstream> // 包含标准字符串流的功能


// 定义Eigen库中的矩阵类型来存储顶点和面
Eigen::MatrixXd V; // V是存储网格顶点坐标的矩阵
Eigen::MatrixXi F; // F是存储网格面顶点索引的矩阵


// 主函数
int main(int argc, char *argv[])
{
  // 加载一个OFF格式的网格
  igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F); // 使用igl::readOFF函数读取“bunny.off”模型的顶点和面数据


  // 找到网格模型的包围盒(bounding box)
  Eigen::Vector3d m = V.colwise().minCoeff(); // 计算每个维度的最小值,得到包围盒的一个角点m
  Eigen::Vector3d M = V.colwise().maxCoeff(); // 计算每个维度的最大值,得到包围盒的对角点M


  // 包围盒的角点
  Eigen::MatrixXd V_box(8,3); // 声明V_box存储包围盒的8个角点
  V_box <<
  m(0), m(1), m(2),
  M(0), m(1), m(2),
  M(0), M(1), m(2),
  m(0), M(1), m(2),
  m(0), m(1), M(2),
  M(0), m(1), M(2),
  M(0), M(1), M(2),
  m(0), M(1), M(2);
  // 包围盒的边
  Eigen::MatrixXi E_box(12,2); // 声明E_box存储包围盒的12条边的顶点索引
  E_box <<
  0, 1,
  1, 2,
  2, 3,
  3, 0,
  4, 5,
  5, 6,
  6, 7,
  7, 4,
  0, 4,
  1, 5,
  2, 6,
  7 ,3;


  // 绘制网格
  igl::opengl::glfw::Viewer viewer; // 创建一个查看器实例
  viewer.data().set_mesh(V, F); // 设置查看器中的网格数据


  // 绘制包围盒角点作为点
  viewer.data().add_points(V_box,Eigen::RowVector3d(1,0,0)); // 在查看器中添加包围盒角点,并设置颜色为红色


  // 绘制包围盒的边
  for (unsigned i=0;i<E_box.rows(); ++i)
    viewer.data().add_edges
    (
      V_box.row(E_box(i,0)), // 边的一个顶点
      V_box.row(E_box(i,1)), // 边的另一个顶点
      Eigen::RowVector3d(1,0,0) // 边的颜色(红色)
    );


  // 绘制包围盒顶点坐标的标签
  std::stringstream l1;
  l1 << m(0) << ", " << m(1) << ", " << m(2);
  viewer.data().add_label(m+Eigen::Vector3d(-0.007, 0, 0),l1.str()); // 添加标签显示m点坐标
  std::stringstream l2;
  l2 << M(0) << ", " << M(1) << ", " << M(2);
  viewer.data().add_label(M+Eigen::Vector3d(0.007, 0, 0),l2.str()); // 添加标签显示M点坐标
  // 激活标签渲染
  viewer.data().show_custom_labels = true; // 设置查看器显示自定义标签


  // ImGui插件处理文本标签渲染,因此我们需要启用ImGui插件以显示文本标签。
  igl::opengl::glfw::imgui::ImGuiPlugin plugin; // 创建ImGui插件实例
  viewer.plugins.push_back(&plugin); // 将ImGui插件添加到查看器插件列表中
  igl::opengl::glfw::imgui::ImGuiMenu menu; // 创建ImGui菜单实例
  plugin.widgets.push_back(&menu); // 将ImGui菜单添加到插件的小部件列表中
  menu.callback_draw_viewer_window = [](){}; // 设置菜单的回调函数,此处为空


  // 启动查看器
  viewer.launch(); // 启动查看器窗口,并渲染显示网格模型及其包围盒和标签
}

六、ViewerMenu

5b92b0eff61d60ec82b6fcdb58b64d26.png

以上示例代码演示了如何使用libigl库和ImGui进行三维网格可视化以及创建一个简单的用户界面(UI)。主要内容包括:

利用igl::readOFF函数从指定路径加载OFF格式的三维网格模型文件(如"bunny.off",兔子模型)。

初始化libigl的OpenGL查看器igl::opengl::glfw::Viewer,用于显示三维网格。

附加ImGui插件igl::opengl::glfw::imgui::ImGuiPlugin到查看器,并创建了一个菜单igl::opengl::glfw::imgui::ImGuiMenu,将该菜单添加到插件的小部件列表中。ImGui是一个即时模式图形用户界面库,用于创建交互且简洁的用户界面。

自定义ImGui菜单,包括添加一个新的折叠头部"New Group",并在其中创建不同类型的控件,例如输入框(用于输入双精度浮点数和字符串)、复选框、下拉框和按钮。同时,展示了如何响应用户的输入与互动(如打印文本到控制台)。

绘制一个额外的ImGui窗口,并在其中显示并允许用户交互修改双精度变量。

在查看器中设置网格数据,并添加一个标签"Hello World!"显示在第一个顶点的临近位置。

启动查看器,显示网格和用户界面,用户可以与之互动。

总的来说,这段代码不仅展示了三维模型的加载与呈现,还涉及了用户界面的交互和可视化,通过ImGui为用户提供了一套操作界面,用以实时交互显示参数等信息,并可以根据用户操作进行相应的动作或显示效果。

// 包含必要的头文件
#include <igl/readOFF.h> // 包含用于读取OFF文件格式的函数
#include <igl/opengl/glfw/Viewer.h> // 包含用于渲染和显示三维网格的查看器类
#include <igl/opengl/glfw/imgui/ImGuiPlugin.h> // 包含libigl集成的ImGui插件相关功能
#include <igl/opengl/glfw/imgui/ImGuiMenu.h> // 包含用于操作ImGui菜单的函数
#include <igl/opengl/glfw/imgui/ImGuiHelpers.h> // 包含ImGui辅助函数
#include <iostream> // 包含标准输入输出流功能


// 主函数
int main(int argc, char *argv[])
{
  Eigen::MatrixXd V; // 定义用于存储网格顶点的矩阵
  Eigen::MatrixXi F; // 定义用于存储网格面索引的矩阵


  // 加载一个OFF格式的网格
  igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F); // 使用igl::readOFF函数读取“bunny.off”模型的顶点和面数据


  // 初始化查看器
  igl::opengl::glfw::Viewer viewer; // 创建查看器实例


  // 附加一个菜单插件
  igl::opengl::glfw::imgui::ImGuiPlugin plugin; // 创建ImGui插件实例
  viewer.plugins.push_back(&plugin); // 将插件添加到查看器的插件列表中
  igl::opengl::glfw::imgui::ImGuiMenu menu; // 创建ImGui菜单实例
  plugin.widgets.push_back(&menu); // 将菜单添加到插件的小部件列表中


  // 自定义菜单
  double doubleVariable = 0.1f; // 在两个菜单之间共享的变量


  // 向默认菜单窗口添加内容
  menu.callback_draw_viewer_menu = [&]()
  {
    // 绘制父菜单内容
    menu.draw_viewer_menu();


    // 添加新分组
    if (ImGui::CollapsingHeader("New Group", ImGuiTreeNodeFlags_DefaultOpen))
    {
      // 直接暴露变量...
      ImGui::InputDouble("double", &doubleVariable, 0, 0, "%.4f");


      // ...或使用自定义回调
      static bool boolVariable = true;
      if (ImGui::Checkbox("bool", &boolVariable))
      {
        // 做些事情
        std::cout << "boolVariable: " << std::boolalpha << boolVariable << std::endl;
      }


      // 暴露枚举类型变量
      enum Orientation { Up=0, Down, Left, Right };
      static Orientation dir = Up;
      ImGui::Combo("Direction", (int *)(&dir), "Up\0Down\0Left\0Right\0\0");


      // 我们也可以动态定义一个std::vector<std::string>
        static int num_choices = 3; // 初始化一个静态整数变量num_choices来指定选择项的数量,默认是3
        static std::vector<std::string> choices; // 初始化一个静态的字符串向量choices来存放下拉列表中的选项
        static int idx_choice = 0; // 初始化一个静态整数变量idx_choice来存储当前选择的下拉列表项的索引,默认选择第一个选项
      
        // 创建一个输入框,用户可以输入字母的数量,并对用户的输入做出响应
        if (ImGui::InputInt("Num letters", &num_choices))
        {
          // 对用户输入的数量进行限制,确保它在1到26之间
          num_choices = std::max(1, std::min(26, num_choices));
        }
      
        // 如果用户输入的字母数量与当前选项数量不同,更新下拉列表的选项
        if (num_choices != (int) choices.size())
        {
          // 根据用户指定的数量调整选项列表的大小
          choices.resize(num_choices);
          // 填充选项列表,从"A"开始,按字母顺序添加
          for (int i = 0; i < num_choices; ++i)
            choices[i] = std::string(1, 'A' + i);
          // 如果当前选择的索引超出了新的选项范围,将其重置为最后一个选项
          if (idx_choice >= num_choices)
            idx_choice = num_choices - 1;
        }
      
        // 创建一个下拉菜单,用户可以从中选择字母
        ImGui::Combo("Letter", &idx_choice, choices);


      // 添加按钮
      if (ImGui::Button("Print Hello", ImVec2(-1,0)))
      {
        std::cout << "Hello\n";
      }
    }
  };


  // 绘制额外窗口
  menu.callback_draw_custom_window = [&]()
  {
    // 定义下一个窗口的位置和大小
     // 设置下一个ImGui窗口的位置
      ImGui::SetNextWindowPos(ImVec2(180.f * menu.menu_scaling(), 10), ImGuiCond_FirstUseEver);
      // 设置下一个ImGui窗口的大小
      ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiCond_FirstUseEver);
      // 创建一个新的ImGui窗口 "New Window"
      ImGui::Begin(
          "New Window", nullptr, // 窗口标题为 "New Window", 不使用额外的窗口标志位
          ImGuiWindowFlags_NoSavedSettings // 设置ImGui窗口标志,指示不保存设置
      );
    
      // 开始定义窗口内的控件内容
      // 设置拖动操作的宽度
      ImGui::PushItemWidth(-80); 
      // 创建一个拖动控件,用于修改double类型的变量doubleVariable,步长为0.1
      ImGui::DragScalar("double", ImGuiDataType_Double, &doubleVariable, 0.1, 0, 0, "%.4f");
      // 恢复之前设置的项目宽度
      ImGui::PopItemWidth();
    
      // 定义一个静态的字符串str并初始化为 "bunny"
      static std::string str = "bunny";
      // 创建一个文本输入框,用于编辑字符串str的内容,并标记为 "Name"
      ImGui::InputText("Name", str);
    
      // 结束定义当前窗口,渲染在屏幕上
      ImGui::End();
  };


  // 绘制网格
  viewer.data().set_mesh(V, F); // 在查看器中设置网格
  // 添加“Hello World!”标签在第一个顶点上,并且偏移一个顶点法线的长度
  viewer.data().add_label(viewer.data().V.row(0) + viewer.data().V_normals.row(0).normalized()*0.005, "Hello World!");
  viewer.launch(); // 启动查看器
}

七、MultipleMeshes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值