7.04 有符号距离
这段代码中使用了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),并可视化结果。代码的执行流畅为:
加载.mesh格式的3D体网格。
初始化AABB树和快速绕组数(Fast Winding Number)BVH。
根据网格和切片水平位置,生成切片网格。
对切片网格计算有无符号距离。
更新igl::opengl::glfw::Viewer中显示的网格,颜色和数据。
用户通过键盘操作如空格键、逗号和句号来改变可视化设置,'1' 和 '2' 来切换签名方法。
代码使用igl::maching_tets
提取体网格中与切片平面相交的等值面,然后使用igl::upsample
对不整齐的三角形进行细分,直至满足所需的精度。通过调整slice_z
变量,用户可以在3D网格中移动切片平面位置。
为了展示切片等值面上有符号距离的可视化效果,代码计算在等值面网格顶点上的有符号距离,并将其映射到颜色条的颜色上。用户可以通过按键'1'和'2'在快速绕组数和伪法线方法之间切换,计算有符号距离。此外,按空格键可切换是否叠加显示原始网格表面。
The End