Files
igl/tutorial/1001_LipschitzOctree/main.cpp
T
Alec Jacobson 47b557df58 Lipschitz octree pruning (#2488) [skip ci]
* lipshitz_octree tutorial example

* better comments

* revert to shallow default

* better names and documentation, dont return I

* long long -> std::int64_t

* asdd tutorail chapter 10

* precious windows

* int64_t -> std::int64_t

* batch version

* fix template chain from overzealous index typing

* bump to c++17 (if constexpr)
2025-07-16 21:20:22 -04:00

238 lines
7.8 KiB
C++

#include <igl/read_triangle_mesh.h>
#include <igl/lipschitz_octree.h>
#include <igl/opengl/glfw/Viewer.h>
#include <igl/AABB.h>
#include <igl/fast_winding_number.h>
#include <igl/signed_distance.h>
#include <igl/grid.h>
#include <igl/colormap.h>
#include <igl/matlab_format.h>
#include <igl/marching_cubes.h>
#include <igl/unique_sparse_voxel_corners.h>
#include <igl/get_seconds.h>
// Edges of a cube box with minimum corner at origin and side length h.
void box_edges(
const Eigen::RowVector3d & origin,
const double h,
Eigen::MatrixXd & V,
Eigen::MatrixXi & E)
{
V.resize(8,3);
V<< 0,0,0, 0,0,1, 0,1,0, 0,1,1, 1,0,0, 1,0,1, 1,1,0, 1,1,1;
V *= h;
V.rowwise() += origin;
E.resize(12,2);
E<< 0,1, 0,2, 0,4, 1,3, 1,5, 2,3, 2,6, 3,7, 4,5, 4,6, 5,7, 6,7;
}
// Edges of cubes placed at all corners of octree with origin and cell side length h.
void all_box_edges(
const Eigen::RowVector3d & origin,
const double h,
const Eigen::Matrix<int,Eigen::Dynamic,3,Eigen::RowMajor> & ijk,
Eigen::MatrixXd & V,
Eigen::MatrixXi & E)
{
V.resize(ijk.rows()*8,3);
E.resize(ijk.rows()*12,2);
for(int c = 0;c<ijk.rows();c++)
{
Eigen::MatrixXd Vc;
Eigen::MatrixXi Ec;
box_edges(
origin + h * Eigen::RowVector3d(ijk(c,1),ijk(c,0),ijk(c,2)), h, Vc,Ec);
V.middleRows(c*8,8) = Vc;
E.middleRows(c*12,12) = Ec.array() + c*8;
}
}
int main(int argc, char * argv[])
{
IGL_TICTOC_LAMBDA;
// Read mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;
if(!igl::read_triangle_mesh
(argc>1?argv[1]: TUTORIAL_SHARED_PATH "/decimated-knight.off",V,F)) {
std::cout << "Failed to load mesh." << std::endl;
}
// Prepare unsigned and signed distance function handles
igl::AABB<Eigen::MatrixXd,3> aabb;
aabb.init(V,F);
const double bbd = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm();
double offset_factor = 0.05;
igl::FastWindingNumberBVH fwn_bvh;
igl::fast_winding_number(V.cast<float>().eval(), F, 2, fwn_bvh);
const std::function<double(const Eigen::RowVector3d &)>
sdf = [&]( const Eigen::RowVector3d & p) -> double
{
double w = fast_winding_number(fwn_bvh, 2, p.template cast<float>().eval());
double s = 1.-2.*std::abs(w);
int i;
Eigen::RowVector3d c;
return s*std::sqrt(aabb.squared_distance(V,F,p,i,c)) - offset_factor*bbd;
};
// This can't be auto when using static library or compiler gets confused.
const std::function<double(const Eigen::RowVector3d &)>
udf = [&]( const Eigen::RowVector3d & p) -> double
{
return std::abs(sdf(p));
};
// Centered bounding cube (root of octree) with padding.
double h0 = (V.colwise().maxCoeff() - V.colwise().minCoeff()).maxCoeff();
int max_depth = 5;
// Pad bounding box by max_depth
//{
// double leaf_h = h0 / (1 << max_depth);
// h0 += leaf_h*2;
//}
// Pad to root
h0 *= 2;
Eigen::RowVector3d origin = 0.5*(V.colwise().maxCoeff() + V.colwise().minCoeff()).array() - 0.5*h0;
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V,F);
const int main_index = viewer.selected_data_index;
Eigen::MatrixXd rV;
Eigen::MatrixXi rE;
box_edges(origin,h0,rV,rE);
viewer.data().set_edges(rV,rE,Eigen::RowVector3d(1,1,0));
viewer.data().set_face_based(true);
viewer.data().show_lines = false;
viewer.append_mesh();
const int aux_index = viewer.selected_data_index;
const auto update = [&]()
{
if(max_depth<=8)
{
// For comparison, how long would it take to compute on the dense grid
tictoc();
Eigen::Matrix<double,Eigen::Dynamic,3,Eigen::RowMajor> GV;
Eigen::RowVector3i res(1<<max_depth,1<<max_depth,1<<max_depth);
igl::grid(res,GV);
GV *= h0;
GV.rowwise() += origin;
Eigen::VectorXd GS(GV.rows());
igl::parallel_for( GV.rows(), [&](const int u)
{
// evaluate the function at the corner
GS(u) = sdf(GV.row(u));
},1000);
printf(" n³ grid: %0.7f seconds\n",tictoc());
}else
{
printf(" n³ grid: [omitted]\n");
}
tictoc();
// Identify the octree cells that could contain the surface
Eigen::Matrix<int,Eigen::Dynamic,3,Eigen::RowMajor> ijk;
igl::lipschitz_octree(origin,h0,max_depth,udf,ijk);
printf(" lipschitz_octree: %0.7f seconds\n",tictoc());
// Gather the corners of those leaf cells
const double h = h0 / (1 << max_depth);
Eigen::Matrix<int,Eigen::Dynamic,8,Eigen::RowMajor> J;
Eigen::Matrix<int,Eigen::Dynamic,3,Eigen::RowMajor> unique_ijk;
Eigen::Matrix<double,Eigen::Dynamic,3,Eigen::RowMajor> unique_corner_positions;
igl::unique_sparse_voxel_corners(origin,h0,max_depth,ijk,unique_ijk,J,unique_corner_positions);
printf("unique_sparse_voxel_corners: %0.7f seconds\n",tictoc());
/// Evaluate the signed distance function at the corners
Eigen::VectorXd S(unique_corner_positions.rows());
//for(int u = 0;u<unique_corner_positions.rows();u++)
igl::parallel_for(
unique_corner_positions.rows(),
[&](const int u)
{
// evaluate the function at the corner
S(u) = sdf(unique_corner_positions.row(u));
},1000);
printf(" sdf: %0.7f seconds\n",tictoc());
// Run marching cubes on the sparse set of leaf cells
Eigen::Matrix<double,Eigen::Dynamic,3> mV;
Eigen::Matrix<int,Eigen::Dynamic,3> mF;
igl::marching_cubes( S,unique_corner_positions,J, 0, mV,mF);
printf(" marching_cubes: %0.7f seconds\n",tictoc());
// Visualize
viewer.data(aux_index).clear();
viewer.data(aux_index).set_mesh(mV,mF);
viewer.data(aux_index).set_face_based(true);
viewer.data().show_lines = max_depth <= 7;
viewer.data(aux_index).set_colors(Eigen::RowVector3d(0.941176,0.305882,0.282353));
if(max_depth <= 7)
{
// If tree is shallow enough, visualize the edges of the octree cells
Eigen::MatrixXd gV;
Eigen::MatrixXi gE;
all_box_edges(origin,h,ijk,gV,gE);
viewer.data(aux_index).set_edges(gV,gE,Eigen::RowVector3d(1,1,1));
Eigen::MatrixXd point_colors;
igl::colormap(
igl::ColorMapType::COLOR_MAP_TYPE_TURBO,
S,-S.array().abs().maxCoeff(),S.array().abs().maxCoeff(),
point_colors);
viewer.data(aux_index).add_points(unique_corner_positions,point_colors);
viewer.data(aux_index).point_size = 8;
}
printf(" |ijk| : %ld\n",ijk.rows());
};
viewer.callback_key_pressed =
[&](igl::opengl::glfw::Viewer & viewer, unsigned int key, int mod)->bool
{
switch(key) {
default:
return false;
case '=':
case '+':
case '-':
case '_':
max_depth = std::max(0,max_depth + ((key == '-'||key=='_') ? -1 : 1));
printf("max_depth = %d\n",max_depth);
update();
break;
case '}':
case '{':
offset_factor =
std::min(std::max(-1.0,offset_factor+(key=='{'?-0.01:0.01)),1.0);
printf("offset_factor = %f\n",offset_factor);
update();
break;
}
return true;
};
update();
// Trivial batched function
const std::function<Eigen::VectorXd(const Eigen::Matrix<double,Eigen::Dynamic,3,Eigen::RowMajor> &)>
udf_batch = [&](const Eigen::Matrix<double,Eigen::Dynamic,3,Eigen::RowMajor> & P) -> Eigen::VectorXd
{
Eigen::VectorXd U(P.rows());
for(int i = 0;i<P.rows();i++)
{
U(i) = udf(P.row(i));
}
return U;
};
{
// The batched version should produce the same size output
Eigen::Matrix<int,Eigen::Dynamic,3,Eigen::RowMajor> ijk_batch;
igl::lipschitz_octree<true>(origin,h0,max_depth,udf_batch,ijk_batch);
printf(" |ijk_batch| : %ld\n",ijk_batch.rows());
}
std::cout << R"(Usage:
-,+ Decrease,Increase the depth of the octree and recompute
{,} Decrease,Increase the offset factor for the signed distance function
)";
viewer.launch();
return 0;
}