diff --git a/src/MassMatrixType.cpp b/src/MassMatrixType.cpp new file mode 100644 index 0000000..94a2d08 --- /dev/null +++ b/src/MassMatrixType.cpp @@ -0,0 +1,20 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + + +void bind_MassMatrixType(nb::module_ &m) +{ + nb::enum_(m, "MassMatrixType") + .value("MASSMATRIX_TYPE_BARYCENTRIC", igl::MASSMATRIX_TYPE_BARYCENTRIC) + .value("MASSMATRIX_TYPE_VORONOI", igl::MASSMATRIX_TYPE_VORONOI) + .value("MASSMATRIX_TYPE_FULL", igl::MASSMATRIX_TYPE_FULL) + .value("MASSMATRIX_TYPE_DEFAULT", igl::MASSMATRIX_TYPE_DEFAULT) + .export_values() + ; +} diff --git a/src/cotmatrix_intrinsic.cpp b/src/cotmatrix_intrinsic.cpp new file mode 100644 index 0000000..c08f15b --- /dev/null +++ b/src/cotmatrix_intrinsic.cpp @@ -0,0 +1,43 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto cotmatrix_intrinsic( + const nb::DRef &l, + const nb::DRef &F) + { + Eigen::SparseMatrixN L; + igl::cotmatrix_intrinsic(l,F,L); + return L; + } +} + +// Bind the wrapper to the Python module +void bind_cotmatrix_intrinsic(nb::module_ &m) +{ + m.def( + "cotmatrix_intrinsic", + &pyigl::cotmatrix_intrinsic, + "l"_a, + "F"_a, +R"( +Constructs the cotangent stiffness matrix (discrete laplacian) for a given +mesh with faces F and edge lengths l. + +@param[in] l #F by 3 list of (half-)edge lengths +@param[in] F #F by 3 list of face indices into some (not necessarily + determined/embedable) list of vertex positions V. It is assumed #V == + F.maxCoeff()+1 +@param[out] L #V by #V sparse Laplacian matrix + +\see cotmatrix, intrinsic_delaunay_cotmatrix)"); +} diff --git a/src/heat_geodesics.cpp b/src/heat_geodesics.cpp new file mode 100644 index 0000000..e7e276a --- /dev/null +++ b/src/heat_geodesics.cpp @@ -0,0 +1,89 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + void heat_geodesics_precompute_t( + const nb::DRef &V, + const nb::DRef &F, + const Numeric t, + igl::HeatGeodesicsData &data) + { + if(!igl::heat_geodesics_precompute(V,F,t,data)) + { + throw std::runtime_error("heat_geodesics: Precomputation failed."); + } + } + // Obnoxious way to have optional t + void heat_geodesics_precompute( + const nb::DRef &V, + const nb::DRef &F, + igl::HeatGeodesicsData &data) + { + if(!igl::heat_geodesics_precompute(V,F,data)) + { + throw std::runtime_error("heat_geodesics: Precomputation failed."); + } + } + + auto heat_geodesics_solve( + const igl::HeatGeodesicsData &data, + const nb::DRef &gamma) + { + Eigen::VectorXN D; + igl::heat_geodesics_solve(data, gamma, D); + return D; + } + +} + +// Bind the wrapper to the Python module +void bind_heat_geodesics(nb::module_ &m) +{ + nb::class_>(m, "HeatGeodesicsData") + .def(nb::init<>()) + .def_rw("use_intrinsic_delaunay", &igl::HeatGeodesicsData::use_intrinsic_delaunay) + ; + + m.def("heat_geodesics_precompute", + &pyigl::heat_geodesics_precompute_t, + "V"_a, "F"_a, "t"_a, "data"_a, + R"(Precompute factorized solvers for computing a fast approximation of +geodesic distances on a mesh (V,F). [Crane et al. 2013] + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh face indices into V +@param[in] t "heat" parameter (smaller --> more accurate, less stable) +@param[out] data precomputation data (see heat_geodesics_solve) + )"); + m.def("heat_geodesics_precompute", + &pyigl::heat_geodesics_precompute, + "V"_a, "F"_a, "data"_a, + R"(Precompute factorized solvers for computing a fast approximation of +geodesic distances on a mesh (V,F). [Crane et al. 2013] + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh face indices into V +@param[out] data precomputation data (see heat_geodesics_solve) + )"); + m.def("heat_geodesics_solve", + &pyigl::heat_geodesics_solve, + "data"_a, + "gamma"_a, + R"(Compute fast approximate geodesic distances using precomputed data from a +set of selected source vertices (gamma). + +@param[in] data precomputation data (see heat_geodesics_precompute) +@param[in] gamma #gamma list of indices into V of source vertices +@param[out] D #V list of distances to gamma + +\fileinfo + )"); +} diff --git a/src/icosahedron.cpp b/src/icosahedron.cpp new file mode 100644 index 0000000..25f26ad --- /dev/null +++ b/src/icosahedron.cpp @@ -0,0 +1,35 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto icosahedron() + { + Eigen::MatrixXN V; + Eigen::MatrixXI F; + igl::icosahedron(V,F); + return std::make_tuple(V,F); + } +} + +// Bind the wrapper to the Python module +void bind_icosahedron(nb::module_ &m) +{ + m.def( + "icosahedron", + &pyigl::icosahedron, +R"( +Construct a icosahedron with radius 1 centered at the origin + +Outputs: + V #V by 3 list of vertex positions + F #F by 3 list of triangle indices into rows of V)"); +} + diff --git a/src/intrinsic_delaunay_cotmatrix.cpp b/src/intrinsic_delaunay_cotmatrix.cpp new file mode 100644 index 0000000..59223ea --- /dev/null +++ b/src/intrinsic_delaunay_cotmatrix.cpp @@ -0,0 +1,46 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto intrinsic_delaunay_cotmatrix( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::SparseMatrixN L; + Eigen::MatrixXN il; + Eigen::MatrixXI iF; + igl::intrinsic_delaunay_cotmatrix(V,F,L,il,iF); + return std::make_tuple(L,il,iF); + } +} + +// Bind the wrapper to the Python module +void bind_intrinsic_delaunay_cotmatrix(nb::module_ &m) +{ + m.def( + "intrinsic_delaunay_cotmatrix", + &pyigl::intrinsic_delaunay_cotmatrix, + "V"_a, + "F"_a, +R"( +Computes the discrete cotangent Laplacian of a mesh after converting it +into its intrinsic Delaunay triangulation (see, e.g., [Fisher et al. +2007]. + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh elements (triangles or tetrahedra) +@param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:) +@param[out] l_intrinsic #F by 3 list of intrinsic edge-lengths used to compute L +@param[out] F_intrinsic #F by 3 list of intrinsic face indices used to compute L + +\see intrinsic_delaunay_triangulation, cotmatrix, cotmatrix_intrinsic)"); +} diff --git a/src/intrinsic_delaunay_triangulation.cpp b/src/intrinsic_delaunay_triangulation.cpp new file mode 100644 index 0000000..0b141fe --- /dev/null +++ b/src/intrinsic_delaunay_triangulation.cpp @@ -0,0 +1,63 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto intrinsic_delaunay_triangulation( + const nb::DRef &l_in, + const nb::DRef &F_in) + { + Eigen::MatrixXN l; + Eigen::MatrixXI F,E,uE; + Eigen::VectorXI EMAP; + std::vector > uE2E; + igl::intrinsic_delaunay_triangulation(l_in,F_in,l,F,E,uE,EMAP,uE2E); + return std::make_tuple(l,F,E,uE,EMAP,uE2E); + + } +} + +// Bind the wrapper to the Python module +void bind_intrinsic_delaunay_triangulation(nb::module_ &m) +{ + m.def( + "intrinsic_delaunay_triangulation", + &pyigl::intrinsic_delaunay_triangulation, + "l"_a, + "F"_a, +R"( +INTRINSIC_DELAUNAY_TRIANGULATION Flip edges _intrinsically_ until all are +"intrinsic Delaunay". See "An algorithm for the construction of intrinsic +delaunay triangulations with applications to digital geometry processing" +[Fisher et al. 2007]. + +@param[in] l_in #F_in by 3 list of edge lengths (see edge_lengths) +@param[in] F_in #F_in by 3 list of face indices into some unspecified vertex list V +@param[out] l #F by 3 list of edge lengths +@param[out] F #F by 3 list of new face indices. Note: Combinatorially F may contain + non-manifold edges, duplicate faces and self-loops (e.g., an edge [1,1] + or a face [1,1,1]). However, the *intrinsic geometry* is still + well-defined and correct. See [Fisher et al. 2007] Figure 3 and 2nd to + last paragraph of 1st page. Since F may be "non-eddge-manifold" in the + usual combinatorial sense, it may be useful to call the more verbose + overload below if disentangling edges will be necessary later on. + Calling unique_edge_map on this F will give a _different_ result than + those outputs. +@param[out] E #F*3 by 2 list of all directed edges, such that E.row(f+#F*c) is the +@param[out] edge opposite F(f,c) +@param[out] uE #uE by 2 list of unique undirected edges +@param[out] EMAP #F*3 list of indices into uE, mapping each directed edge to unique +@param[out] undirected edge +@param[out] uE2E #uE list of lists of indices into E of coexisting edges + +\see unique_edge_map +)"); +} diff --git a/src/massmatrix.cpp b/src/massmatrix.cpp index 07ac751..f22e334 100644 --- a/src/massmatrix.cpp +++ b/src/massmatrix.cpp @@ -25,13 +25,6 @@ namespace pyigl // Bind the wrapper to the Python module void bind_massmatrix(nb::module_ &m) { - nb::enum_(m, "MassMatrixType") - .value("MASSMATRIX_TYPE_BARYCENTRIC", igl::MASSMATRIX_TYPE_BARYCENTRIC) - .value("MASSMATRIX_TYPE_VORONOI", igl::MASSMATRIX_TYPE_VORONOI) - .value("MASSMATRIX_TYPE_FULL", igl::MASSMATRIX_TYPE_FULL) - .value("MASSMATRIX_TYPE_DEFAULT", igl::MASSMATRIX_TYPE_DEFAULT) - .export_values() - ; m.def( "massmatrix", &pyigl::massmatrix, diff --git a/src/massmatrix_intrinsic.cpp b/src/massmatrix_intrinsic.cpp new file mode 100644 index 0000000..e758646 --- /dev/null +++ b/src/massmatrix_intrinsic.cpp @@ -0,0 +1,45 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto massmatrix_intrinsic( + const nb::DRef &l, + const nb::DRef &F, + const igl::MassMatrixType type) + { + Eigen::SparseMatrixN M; + igl::massmatrix_intrinsic(l,F,type,M); + return M; + } +} + +// Bind the wrapper to the Python module +void bind_massmatrix_intrinsic(nb::module_ &m) +{ + m.def( + "massmatrix_intrinsic", + &pyigl::massmatrix_intrinsic, + "l"_a, + "F"_a, + "type"_a=igl::MASSMATRIX_TYPE_DEFAULT, +R"( +Constructs the mass matrix for a given +mesh with faces F and edge lengths l. + +@param[in] l #F by 3 list of (half-)edge lengths +@param[in] F #F by 3 list of face indices into some (not necessarily + determined/embedable) list of vertex positions V. It is assumed #V == + F.maxCoeff()+1 +@param[out] L #V by #V sparse Laplacian matrix + +\see massmatrix, intrinsic_delaunay_massmatrix)"); +} diff --git a/src/matlab_format.cpp b/src/matlab_format.cpp index b4dfbed..26fc3d4 100644 --- a/src/matlab_format.cpp +++ b/src/matlab_format.cpp @@ -56,6 +56,13 @@ void bind_matlab_format(nb::module_ &m) "name"_a = "", R"(Format a dense matrix for MATLAB-style output.)"); + m.def( + "matlab_format", + &pyigl::matlab_format_dense_vector, + "M"_a, + "name"_a = "", + R"(Format a dense matrix for MATLAB-style output.)"); + m.def( "matlab_format", &pyigl::matlab_format_sparse, @@ -77,13 +84,6 @@ void bind_matlab_format(nb::module_ &m) "name"_a = "", R"(Format a matrix for MATLAB-style output with 1-based indexing.)"); - m.def( - "matlab_format", - &pyigl::matlab_format_dense_vector, - "M"_a, - "name"_a = "", - R"(Format a dense matrix for MATLAB-style output.)"); - m.def( "matlab_format_index", &pyigl::matlab_format_index_vector, diff --git a/tests/test.py b/tests/test.py index fb1ba8f..ecb7559 100644 --- a/tests/test.py +++ b/tests/test.py @@ -113,7 +113,6 @@ W = igl.harmonic(V,F,b,bc,k=1) W = igl.harmonic(V,F,b,bc,k=2) - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) T = np.array([[0,1,2,3]],dtype=np.int64) @@ -351,6 +350,24 @@ I,C = igl.polygon_corners(Q) F,J = igl.polygons_to_triangles(I,C) +V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +V,F = igl.upsample(V,F,number_of_subdivs=1) +data = igl.HeatGeodesicsData() +igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) +data = igl.HeatGeodesicsData() +data.use_intrinsic_delaunay = True +igl.heat_geodesics_precompute(V,F,data) +gamma = np.array([0],dtype=np.int64) +D = igl.heat_geodesics_solve(data,gamma) +l = igl.edge_lengths(V,F) +iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) +L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) +L = igl.cotmatrix_intrinsic(il,iF) +M = igl.massmatrix_intrinsic(il,iF) + +V,F = igl.icosahedron() + try: import igl.copyleft V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64)