Skip to content

Commit 1336354

Browse files
authored
Merge pull request libigl#275 from libigl/alecjacobson/lipschitz-octree [skip ci]
lipschitz_octree and unique_sparse_voxel_corners
2 parents 1102621 + 27bd8ab commit 1336354

File tree

7 files changed

+159
-23
lines changed

7 files changed

+159
-23
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON)
4545
FetchContent_Declare(
4646
libigl
4747
GIT_REPOSITORY https://github.com/libigl/libigl.git
48-
GIT_TAG cf9ed7f492209590c42dc7247281dfdfb6618487
48+
GIT_TAG 678e1fff76815e0c4c5d1f025ee2129181cc7d86
4949
)
5050
FetchContent_MakeAvailable(libigl)
5151

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ build-backend = "scikit_build_core.build"
1414

1515
[project]
1616
name = "libigl"
17-
version = "2.6.1.dev0"
17+
version = "2.6.2.dev0"
1818
description = "libigl: A simple C++ geometry processing library"
1919
readme = "README.md"
2020
requires-python = ">=3.8"

src/lipschitz_octree.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include "default_types.h"
2+
#include <igl/lipschitz_octree.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/ndarray.h>
5+
#include <nanobind/eigen/dense.h>
6+
#include <nanobind/stl/tuple.h>
7+
#include <nanobind/stl/function.h>
8+
9+
namespace nb = nanobind;
10+
using namespace nb::literals;
11+
12+
namespace pyigl
13+
{
14+
// Hopefully this will avoid a copy by matching the type in lipschitz_octree_prune
15+
using MatrixNX3R = Eigen::Matrix<Numeric, Eigen::Dynamic, 3, Eigen::RowMajor>;
16+
auto lipschitz_octree(
17+
const nb::DRef<const Eigen::VectorXN> &origin,
18+
const Numeric h0,
19+
const Integer max_depth,
20+
const nb::typed<
21+
nb::callable,
22+
Eigen::VectorXN(const nb::DRef<const MatrixNX3R> &)> & udf)
23+
{
24+
Eigen::MatrixXI ijk;
25+
// Instead of MatrixXN if use use MatrixNX3R we might avoid a copy
26+
const std::function<Eigen::VectorXN(const MatrixNX3R &)> udf_wrapper =
27+
[&](const MatrixNX3R &Q) -> Eigen::VectorXN
28+
{
29+
return nb::cast<Eigen::VectorXN>(udf(Q));
30+
};
31+
igl::lipschitz_octree<true>(origin, h0, max_depth, udf_wrapper, ijk);
32+
return ijk;
33+
}
34+
}
35+
36+
// Bind the wrapper to the Python module
37+
void bind_lipschitz_octree(nb::module_ &m)
38+
{
39+
m.def(
40+
"lipschitz_octree",
41+
&pyigl::lipschitz_octree,
42+
"origin"_a,
43+
"h0"_a,
44+
"max_depth"_a,
45+
"udf"_a,
46+
R"(Given a minimum corner position (origin) and a side length (h0) and a
47+
maximum depth (max_depth), determine the possible active leaf octree cells
48+
based on an one-Lipschitz non-negative function to a level set (e.g.,
49+
"unsigned distance function").
50+
51+
@param[in] origin 3-vector of root cell origin (minimum corner)
52+
@param[in] h0 side length of root cell
53+
@param[in] max_depth maximum depth of octree (root is depth=0)
54+
@param[in] udf 1-Lipschitz function of (unsigned) distance to level set to a
55+
list of batched query points
56+
@param[out] ijk #ijk by 3 list of octree leaf cell minimum corner
57+
subscripts
58+
)");
59+
}
60+
61+
62+

src/marching_cubes.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.i
7777
"S"_a,
7878
"GV"_a,
7979
"GI"_a,
80-
"isovalue"_a=0,
80+
"isovalue"_a=0.0,
8181
R"(Performs marching cubes reconstruction on a grid defined by values, and
8282
points, and generates a mesh defined by vertices and faces
8383

src/offset_surface.cpp

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,25 @@ namespace nb = nanobind;
1010
using namespace nb::literals;
1111

1212
namespace pyigl {
13-
auto offset_surface(const nb::DRef<const Eigen::MatrixXN> &V,
14-
const nb::DRef<const Eigen::MatrixXI> &F,
15-
const Numeric isolevel, const Integer s,
16-
const igl::SignedDistanceType signed_distance_type) {
17-
18-
Eigen::MatrixXN SV;
19-
Eigen::MatrixXI SF;
20-
Eigen::MatrixXN GV;
21-
22-
Eigen::VectorXI side;
23-
Eigen::MatrixXN so;
24-
25-
igl::offset_surface(V, F, isolevel, s, signed_distance_type, SV, SF, GV, side,
26-
so);
27-
28-
return std::make_tuple(SV, SF, GV, side, so);
29-
}
13+
auto offset_surface(
14+
const nb::DRef<const Eigen::MatrixXN> &V,
15+
const nb::DRef<const Eigen::MatrixXI> &F,
16+
const Numeric isolevel,
17+
const Integer s,
18+
const igl::SignedDistanceType signed_distance_type)
19+
{
20+
Eigen::MatrixXN SV;
21+
Eigen::MatrixXI SF;
22+
Eigen::MatrixXN GV;
23+
24+
Eigen::VectorXI side;
25+
Eigen::MatrixXN so;
26+
igl::offset_surface(
27+
V, F, isolevel, s, signed_distance_type, SV, SF, GV, side,
28+
so);
29+
30+
return std::make_tuple(SV, SF, GV, side, so);
31+
}
3032
} // namespace pyigl
3133

3234
// Bind the wrapper to the Python module
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "default_types.h"
2+
#include <igl/unique_sparse_voxel_corners.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/ndarray.h>
5+
#include <nanobind/eigen/dense.h>
6+
#include <nanobind/stl/tuple.h>
7+
8+
namespace nb = nanobind;
9+
using namespace nb::literals;
10+
11+
namespace pyigl
12+
{
13+
auto unique_sparse_voxel_corners(
14+
const nb::DRef<const Eigen::VectorXN> &origin,
15+
const Numeric h0,
16+
const Integer depth,
17+
const nb::DRef<const Eigen::MatrixXI> &ijk)
18+
{
19+
Eigen::MatrixXI unique_ijk;
20+
Eigen::MatrixXI J;
21+
Eigen::MatrixXN unique_corners;
22+
23+
igl::unique_sparse_voxel_corners(
24+
origin, h0, depth, ijk, unique_ijk, J, unique_corners);
25+
26+
return std::make_tuple(unique_ijk, J, unique_corners);
27+
}
28+
}
29+
30+
// Bind the wrapper to the Python module
31+
void bind_unique_sparse_voxel_corners(nb::module_ &m)
32+
{
33+
m.def(
34+
"unique_sparse_voxel_corners",
35+
&pyigl::unique_sparse_voxel_corners,
36+
"origin"_a,
37+
"h0"_a,
38+
"depth"_a,
39+
"ijk"_a,
40+
R"(
41+
Give a list of octree cells subscripts (ijk) (minimum corners) at a given depth,
42+
determine a unique list of subscripts to all incident corners of those
43+
cells (de-replicating shared corners).
44+
45+
@param[in] origin 3-vector of root cell minimum
46+
@param[in] h0 side length of current depth level
47+
@param[in] depth current depth (single root cell is depth = 0)
48+
@param[in] ijk #ijk by 3 list of octree leaf cell minimum corner
49+
subscripts
50+
@param[out] unique_ijk #unique_ijk by 3 list of unique corner subscripts
51+
@param[out] J #ijk by 8 list of indices into unique_ijk in yxz binary
52+
counting order
53+
@param[out] unique_corners #unique_ijk by 3 list of unique corner
54+
positions
55+
)");
56+
57+
}
58+
59+
60+

tests/test_all.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,18 @@ def test_misc():
575575
R = igl.oriented_bounding_box(V)
576576
R = igl.oriented_bounding_box(V,n=100,minimize_type=igl.ORIENTED_BOUNDING_BOX_MINIMIZE_SURFACE_AREA)
577577

578-
579-
580-
578+
def test_octree():
579+
580+
def sdf_sphere(Q):
581+
return np.linalg.norm(Q,axis=1) - 0.5
582+
def udf_sphere(Q):
583+
return np.abs(sdf_sphere(Q))
584+
585+
origin = np.array([-1,-1,-1],dtype=np.float64)
586+
h0 = 2.0
587+
max_depth = 4
588+
ijk = igl.lipschitz_octree(origin,h0,max_depth,udf_sphere)
589+
h = h0 / (2**max_depth)
590+
unique_ijk, J, unique_corners = igl.unique_sparse_voxel_corners(origin,h0,max_depth,ijk)
591+
unique_S = sdf_sphere(unique_corners)
592+
V,F = igl.marching_cubes(unique_S,unique_corners,J,0.0)

0 commit comments

Comments
 (0)