From 830880d8d6dcdacd4f4a4f17527034dcbc33977d Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Thu, 29 Dec 2022 22:28:28 -0800 Subject: [PATCH 01/12] Fix tests by fixing numpy datatype checks --- tests/test_basic.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 07ca6049..b356483b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -756,7 +756,7 @@ def test_marching_cubes(self): V,F = igl.marching_cubes(sphereField, pts, n, n, n, 0.0) self.assertTrue(V.dtype == pts.dtype) - self.assertTrue(F.dtype == np.int) + self.assertTrue(F.dtype == np.int64) self.assertNotEqual(V.shape, (0,3)) self.assertNotEqual(F.shape, (0,3)) @@ -1724,8 +1724,8 @@ def test_path_to_edges(self): self.assertTrue(np.allclose(e2, r2)) self.assertTrue(e1.flags.c_contiguous) self.assertTrue(e2.flags.c_contiguous) - self.assertTrue(e1.dtype == np.int) - self.assertTrue(e2.dtype == np.int) + self.assertTrue(e1.dtype == np.int64) + self.assertTrue(e2.dtype == np.int64) def test_exterior_edges(self): e = igl.exterior_edges(self.f1) @@ -2287,9 +2287,9 @@ def test_quad_grid(self): self.assertTrue(q.shape == (2*2, 4)) self.assertTrue(v.flags.c_contiguous) self.assertTrue(q.flags.c_contiguous) - self.assertTrue(v.dtype == np.float) - self.assertTrue(q.dtype == np.int) - self.assertTrue(e.dtype == np.int) + self.assertTrue(v.dtype == np.float64) + self.assertTrue(q.dtype == np.int64) + self.assertTrue(e.dtype == np.int64) def test_sparse_voxel_grid(self): def sphere1(point): @@ -2297,10 +2297,10 @@ def sphere1(point): point = np.array([1.0, 0.0, 0.0]) cs, cv, ci = igl.sparse_voxel_grid(point, sphere1, 1.0, 100) self.assertTrue(cv.flags.c_contiguous) - self.assertTrue(cv.dtype == np.float) + self.assertTrue(cv.dtype == np.float64) self.assertTrue(cv.shape == (len(cs), 3)) self.assertTrue(ci.flags.c_contiguous) - self.assertTrue(ci.dtype == np.int) + self.assertTrue(ci.dtype == np.int64) self.assertTrue(ci.shape[1] == 8) def test_topological_hole_fill(self): @@ -2310,7 +2310,7 @@ def test_topological_hole_fill(self): ff = igl.topological_hole_fill(f, b, h) self.assertTrue(ff.flags.c_contiguous) self.assertTrue(ff.shape[1] == 3) - self.assertTrue(ff.dtype == np.int) + self.assertTrue(ff.dtype == np.int64) self.assertTrue(ff.shape[0] != f.shape[0]) def test_triangulated_grid(self): @@ -2319,8 +2319,8 @@ def test_triangulated_grid(self): self.assertTrue(f.shape == (162, 3)) self.assertTrue(f.flags.c_contiguous) self.assertTrue(v.flags.c_contiguous) - self.assertTrue(v.dtype == np.float) - self.assertTrue(f.dtype == np.int) + self.assertTrue(v.dtype == np.float64) + self.assertTrue(f.dtype == np.int64) def test_unproject_on_line(self): pos = np.array([10., 10.]) @@ -2332,7 +2332,7 @@ def test_unproject_on_line(self): self.assertTrue(z.flags.c_contiguous) self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == np.float) + self.assertTrue(z.dtype == np.float64) def test_unproject_on_plane(self): pos = np.array([10., 10.]) @@ -2343,7 +2343,7 @@ def test_unproject_on_plane(self): self.assertTrue(z.flags.c_contiguous) self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == np.float) + self.assertTrue(z.dtype == np.float64) def test_fast_winding_number_for_points(self): xs = np.linspace(-5.0, 5.0, 10) @@ -2355,7 +2355,7 @@ def test_fast_winding_number_for_points(self): wn = igl.fast_winding_number_for_points(self.v1, n, a, grid) self.assertTrue(wn.flags.c_contiguous) self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == np.float) + self.assertTrue(wn.dtype == np.float64) def test_fast_winding_number_for_meshes(self): xs = np.linspace(-5.0, 5.0, 10) @@ -2365,7 +2365,7 @@ def test_fast_winding_number_for_meshes(self): wn = igl.fast_winding_number_for_meshes(self.v1, self.f1, grid) self.assertTrue(wn.flags.c_contiguous) self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == np.float) + self.assertTrue(wn.dtype == np.float64) def test_flip_avoiding_line_search(self): def fun(v): @@ -2387,7 +2387,7 @@ def test_edge_flaps(self): self.assertTrue(ef.flags.c_contiguous) self.assertTrue(ei.flags.c_contiguous) self.assertTrue(e.dtype == emap.dtype == - ef.dtype == ei.dtype == np.int) + ef.dtype == ei.dtype == np.int64) def test_circulation(self): pass From deb538fd7b2e5e919601dbafc5e6d2f296a10596 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Thu, 29 Dec 2022 23:23:12 -0800 Subject: [PATCH 02/12] Adding python bindings for direct delta mush --- src/direct_delta_mush.cpp | 120 ++++++++++++++++++++++++++++++++++++++ src/include/common.h | 18 ++++++ 2 files changed, 138 insertions(+) create mode 100644 src/direct_delta_mush.cpp diff --git a/src/direct_delta_mush.cpp b/src/direct_delta_mush.cpp new file mode 100644 index 00000000..d242d82c --- /dev/null +++ b/src/direct_delta_mush.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +const char* ds_direct_delta_mush = R"igl_Qu8mg5v7( +Perform Direct Delta Mush Skinning. +Computes Direct Delta Mesh Skinning (Variant 0) from +"Direct Delta Mush Skinning and Variants" + +Parameters +---------- +v #V by 3 list of rest pose vertex positions +e #E Number of bones +t #E*4 by 3 list of bone pose transformations +omega #V by #E*10 list of precomputated matrix values + +Returns +------- +u #V by 3 list of output vertex positions + +See also +-------- + + +Notes +----- +None + +Examples +-------- +)igl_Qu8mg5v7"; + +npe_function(direct_delta_mush) +npe_doc(ds_direct_delta_mush) + +npe_arg(v, dense_float, dense_double) +npe_arg(t, dense_float, dense_double) +npe_arg(omega, dense_float, dense_double) + +npe_begin_code() + assert_cols_equals(v, 3, "v"); + assert_valid_bone_transforms(t, "t"); + assert_rows_equals(t, (omega.cols() * 4) / 10, "t"); + + Eigen::MatrixXd v_copy = v.template cast(); + Eigen::MatrixXd t_copy = t.template cast(); + Eigen::MatrixXd omega_copy = omega.template cast(); + + std::vector> + t_affine(t_copy.rows() / 4); + + for(int bone = 0; bone < t_copy.rows(); bone += 4) + { + Eigen::Matrix4d t_bone; + t_bone << t_copy.block(bone, 0, 4, 3).transpose(), 0.0, 0.0, 0.0, 0.0; + Eigen::Affine3d a_bone; + a_bone.matrix() = t_bone; + + t_affine[bone] = a_bone; + } + + Eigen::MatrixXd u; + igl::direct_delta_mush(v_copy, t_affine, omega_copy, u); + return npe::move(u); + +npe_end_code() + +const char* ds_direct_delta_mush_precomp = R"igl_Qu8mg5v7( +Do the Omega precomputation necessary for Direct Delta Mush Skinning. + +Parameters +---------- +v #V by 3 list of rest pose vertex positions +f #F by 3 list of triangle indices into rows of V +w #V by #Edges list of weights +p number of smoothing iterations +lambda rotation smoothing step size +kappa translation smoothness step size +alpha translation smoothness blending weight + +Returns +------- +omega : #V by #E*10 list of precomputated matrix values + +See also +-------- + + +Notes +----- +None + +Examples +-------- +)igl_Qu8mg5v7"; + +npe_function(direct_delta_mush_precomputation) +npe_doc(ds_direct_delta_mush_precomp) + +npe_arg(v, dense_float, dense_double) +npe_arg(f, dense_int, dense_long, dense_longlong) +npe_arg(w, npe_matches(v)) +npe_arg(p, int) +npe_arg(lambda, double) +npe_arg(kappa, double) +npe_arg(alpha, double) + +npe_begin_code() + assert_valid_3d_tri_mesh(v, f); + + Eigen::MatrixXd v_copy = v.template cast(); + Eigen::MatrixXd w_copy = w.template cast(); + Eigen::MatrixXi f_copy = f.template cast(); + + Eigen::MatrixXd omega; + igl::direct_delta_mush_precomputation(v_copy, f_copy, w_copy, p, lambda, kappa, alpha, omega); + return npe::move(omega); + +npe_end_code() diff --git a/src/include/common.h b/src/include/common.h index c4a2d5cf..a2924e04 100644 --- a/src/include/common.h +++ b/src/include/common.h @@ -311,3 +311,21 @@ void assert_valid_2d_tri_mesh(const TV& v, const TF& f, std::string v_name="v", ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); } } + +template +void assert_valid_bone_transforms(const TV& t, std::string t_name="t") { + if (t.rows() <= 0) { + throw pybind11::value_error("Invalid number of transforms, " + t_name + " has zero rows (" + t_name + + ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]) "); + } + + if (t.cols() != 3) { + throw pybind11::value_error("Invalid number of cols in transforms, " + t_name + " must have shape [#bones * 4, 3] but got " + t_name + + ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]"); + } + + if (t.rows() % 4 != 0) { + throw pybind11::value_error("Invalid number of rows in transforms, " + t_name + " must have shape [#bones * 4, 3] but got " + t_name + + ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]."); + } +} From 921a3897d93344d034d49b0e7c538ef7949e4fb6 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Fri, 30 Dec 2022 10:32:38 -0800 Subject: [PATCH 03/12] Test usage of direct delta mush --- tests/test_basic.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index b356483b..09a47e47 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1528,6 +1528,38 @@ def test_lbs_matrix(self): self.assertTrue(M.shape[0] == V.shape[0]) self.assertTrue(M.shape[1] == W.shape[1]*4) + def test_direct_delta_mush(self): + V, F = igl.read_triangle_mesh(os.path.join(self.test_path, "arm.obj")) + W = igl.read_dmat(os.path.join(self.test_path, "arm-weights.dmat")) + _, BE, _, _, _, _ = igl.read_tgf(os.path.join(self.test_path, "arm.tgf")) + + # Use same values as tutorial + # https://github.com/libigl/libigl/blob/main/tutorial/408_DirectDeltaMush/main.cpp + p = 20 + l = 3 + k = 1 + a = 0.8 + omega = igl.direct_delta_mush_precomputation(V, F,W, p, l, k, a) + + self.assertTrue(omega.shape[0] == V.shape[0]) + self.assertTrue(omega.shape[1] == BE.shape[0] * 10) + + T = np.zeros((BE.shape[0]*4, 3)) + I = np.eye(3) + for i in range(0, T.shape[0], 4): + T[i:i+3, :] = I + + U = igl.direct_delta_mush(V, T, omega) + + self.assertTrue(U.shape[0] == V.shape[0]) + self.assertTrue(U.shape[1] == 3) + self.assertTrue(U.dtype == V.dtype) + + def test_direct_delta_mush_precomputation(self): + # covered in test_direct_delta_mush + pass + + def test_point_mesh_squared_distance(self): dist, i, c = igl.point_mesh_squared_distance( np.array([0., 0., 0.]), self.v1, self.f1) From 0d3476e378f21cf63e3fd997a9b47f63581c6855 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Fri, 30 Dec 2022 15:19:02 -0800 Subject: [PATCH 04/12] Fix bone transformation indexing issue --- src/direct_delta_mush.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/direct_delta_mush.cpp b/src/direct_delta_mush.cpp index d242d82c..24dd3b6b 100644 --- a/src/direct_delta_mush.cpp +++ b/src/direct_delta_mush.cpp @@ -50,10 +50,11 @@ npe_begin_code() std::vector> t_affine(t_copy.rows() / 4); - for(int bone = 0; bone < t_copy.rows(); bone += 4) + for(int bone = 0; bone < t_affine.size(); ++bone) { Eigen::Matrix4d t_bone; - t_bone << t_copy.block(bone, 0, 4, 3).transpose(), 0.0, 0.0, 0.0, 0.0; + t_bone << t_copy.block(bone * 4, 0, 4, 3).transpose(), 0.0, 0.0, 0.0, 0.0; + Eigen::Affine3d a_bone; a_bone.matrix() = t_bone; From eb1a2e020626f5a6a6b0f21cd868843cc62723db Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Sun, 1 Jan 2023 16:33:59 -0800 Subject: [PATCH 05/12] Revert "Fix tests by fixing numpy datatype checks" This reverts commit 830880d8d6dcdacd4f4a4f17527034dcbc33977d. --- tests/test_basic.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 09a47e47..da1b0ae3 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -756,7 +756,7 @@ def test_marching_cubes(self): V,F = igl.marching_cubes(sphereField, pts, n, n, n, 0.0) self.assertTrue(V.dtype == pts.dtype) - self.assertTrue(F.dtype == np.int64) + self.assertTrue(F.dtype == np.int) self.assertNotEqual(V.shape, (0,3)) self.assertNotEqual(F.shape, (0,3)) @@ -1756,8 +1756,8 @@ def test_path_to_edges(self): self.assertTrue(np.allclose(e2, r2)) self.assertTrue(e1.flags.c_contiguous) self.assertTrue(e2.flags.c_contiguous) - self.assertTrue(e1.dtype == np.int64) - self.assertTrue(e2.dtype == np.int64) + self.assertTrue(e1.dtype == np.int) + self.assertTrue(e2.dtype == np.int) def test_exterior_edges(self): e = igl.exterior_edges(self.f1) @@ -2319,9 +2319,9 @@ def test_quad_grid(self): self.assertTrue(q.shape == (2*2, 4)) self.assertTrue(v.flags.c_contiguous) self.assertTrue(q.flags.c_contiguous) - self.assertTrue(v.dtype == np.float64) - self.assertTrue(q.dtype == np.int64) - self.assertTrue(e.dtype == np.int64) + self.assertTrue(v.dtype == np.float) + self.assertTrue(q.dtype == np.int) + self.assertTrue(e.dtype == np.int) def test_sparse_voxel_grid(self): def sphere1(point): @@ -2329,10 +2329,10 @@ def sphere1(point): point = np.array([1.0, 0.0, 0.0]) cs, cv, ci = igl.sparse_voxel_grid(point, sphere1, 1.0, 100) self.assertTrue(cv.flags.c_contiguous) - self.assertTrue(cv.dtype == np.float64) + self.assertTrue(cv.dtype == np.float) self.assertTrue(cv.shape == (len(cs), 3)) self.assertTrue(ci.flags.c_contiguous) - self.assertTrue(ci.dtype == np.int64) + self.assertTrue(ci.dtype == np.int) self.assertTrue(ci.shape[1] == 8) def test_topological_hole_fill(self): @@ -2342,7 +2342,7 @@ def test_topological_hole_fill(self): ff = igl.topological_hole_fill(f, b, h) self.assertTrue(ff.flags.c_contiguous) self.assertTrue(ff.shape[1] == 3) - self.assertTrue(ff.dtype == np.int64) + self.assertTrue(ff.dtype == np.int) self.assertTrue(ff.shape[0] != f.shape[0]) def test_triangulated_grid(self): @@ -2351,8 +2351,8 @@ def test_triangulated_grid(self): self.assertTrue(f.shape == (162, 3)) self.assertTrue(f.flags.c_contiguous) self.assertTrue(v.flags.c_contiguous) - self.assertTrue(v.dtype == np.float64) - self.assertTrue(f.dtype == np.int64) + self.assertTrue(v.dtype == np.float) + self.assertTrue(f.dtype == np.int) def test_unproject_on_line(self): pos = np.array([10., 10.]) @@ -2364,7 +2364,7 @@ def test_unproject_on_line(self): self.assertTrue(z.flags.c_contiguous) self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == np.float64) + self.assertTrue(z.dtype == np.float) def test_unproject_on_plane(self): pos = np.array([10., 10.]) @@ -2375,7 +2375,7 @@ def test_unproject_on_plane(self): self.assertTrue(z.flags.c_contiguous) self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == np.float64) + self.assertTrue(z.dtype == np.float) def test_fast_winding_number_for_points(self): xs = np.linspace(-5.0, 5.0, 10) @@ -2387,7 +2387,7 @@ def test_fast_winding_number_for_points(self): wn = igl.fast_winding_number_for_points(self.v1, n, a, grid) self.assertTrue(wn.flags.c_contiguous) self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == np.float64) + self.assertTrue(wn.dtype == np.float) def test_fast_winding_number_for_meshes(self): xs = np.linspace(-5.0, 5.0, 10) @@ -2397,7 +2397,7 @@ def test_fast_winding_number_for_meshes(self): wn = igl.fast_winding_number_for_meshes(self.v1, self.f1, grid) self.assertTrue(wn.flags.c_contiguous) self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == np.float64) + self.assertTrue(wn.dtype == np.float) def test_flip_avoiding_line_search(self): def fun(v): @@ -2419,7 +2419,7 @@ def test_edge_flaps(self): self.assertTrue(ef.flags.c_contiguous) self.assertTrue(ei.flags.c_contiguous) self.assertTrue(e.dtype == emap.dtype == - ef.dtype == ei.dtype == np.int64) + ef.dtype == ei.dtype == np.int) def test_circulation(self): pass From 63c5bad03e95ab80d4458d686721d9f7065a8c59 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Thu, 5 Jan 2023 18:11:58 -0800 Subject: [PATCH 06/12] Affine transforms weren't being built right --- src/direct_delta_mush.cpp | 21 ++++++++------------- tests/test_basic.py | 1 + 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/direct_delta_mush.cpp b/src/direct_delta_mush.cpp index 24dd3b6b..2c0d7930 100644 --- a/src/direct_delta_mush.cpp +++ b/src/direct_delta_mush.cpp @@ -34,9 +34,9 @@ Examples npe_function(direct_delta_mush) npe_doc(ds_direct_delta_mush) -npe_arg(v, dense_float, dense_double) -npe_arg(t, dense_float, dense_double) -npe_arg(omega, dense_float, dense_double) +npe_arg(v, dense_double) +npe_arg(t, dense_double) +npe_arg(omega, dense_double) npe_begin_code() assert_cols_equals(v, 3, "v"); @@ -52,16 +52,11 @@ npe_begin_code() for(int bone = 0; bone < t_affine.size(); ++bone) { - Eigen::Matrix4d t_bone; - t_bone << t_copy.block(bone * 4, 0, 4, 3).transpose(), 0.0, 0.0, 0.0, 0.0; - - Eigen::Affine3d a_bone; - a_bone.matrix() = t_bone; - - t_affine[bone] = a_bone; + t_affine[bone] = Eigen::Affine3d::Identity(); + t_affine[bone].matrix().block(0, 0, 3, 4) = t_copy.block(bone * 4, 0, 4, 3).transpose(); } - Eigen::MatrixXd u; + EigenDenseLike u; igl::direct_delta_mush(v_copy, t_affine, omega_copy, u); return npe::move(u); @@ -99,7 +94,7 @@ Examples npe_function(direct_delta_mush_precomputation) npe_doc(ds_direct_delta_mush_precomp) -npe_arg(v, dense_float, dense_double) +npe_arg(v, dense_double) npe_arg(f, dense_int, dense_long, dense_longlong) npe_arg(w, npe_matches(v)) npe_arg(p, int) @@ -114,7 +109,7 @@ npe_begin_code() Eigen::MatrixXd w_copy = w.template cast(); Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd omega; + EigenDenseLike omega; igl::direct_delta_mush_precomputation(v_copy, f_copy, w_copy, p, lambda, kappa, alpha, omega); return npe::move(omega); diff --git a/tests/test_basic.py b/tests/test_basic.py index da1b0ae3..d19505c3 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1554,6 +1554,7 @@ def test_direct_delta_mush(self): self.assertTrue(U.shape[0] == V.shape[0]) self.assertTrue(U.shape[1] == 3) self.assertTrue(U.dtype == V.dtype) + self.assertFalse(np.isnan(U).any()) def test_direct_delta_mush_precomputation(self): # covered in test_direct_delta_mush From ea22af0d4b43224c2bea111444252927e30411a2 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Fri, 6 Jan 2023 18:09:40 -0800 Subject: [PATCH 07/12] Updating the commit hash for tutorial and test data download --- cmake/PyiglDownloadExternal.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/PyiglDownloadExternal.cmake b/cmake/PyiglDownloadExternal.cmake index 5846688a..bb2a5163 100644 --- a/cmake/PyiglDownloadExternal.cmake +++ b/cmake/PyiglDownloadExternal.cmake @@ -37,7 +37,7 @@ function(pyigl_download_test_data) pyigl_download_project(test_data SOURCE_DIR "${PYLIBIGL_EXTERNAL}/../data" GIT_REPOSITORY https://github.com/libigl/libigl-tests-data - GIT_TAG fa8ee953a64a560f24553dd8bda0c6cd4fbb5353 + GIT_TAG 19cedf96d70702d8b3a83eb27934780c542356fe ) endfunction() @@ -46,7 +46,7 @@ function(pyigl_download_tutorial_data) pyigl_download_project(tutorial_data SOURCE_DIR "${PYLIBIGL_EXTERNAL}/../tutorial/data" GIT_REPOSITORY https://github.com/libigl/libigl-tutorial-data - GIT_TAG 38bbed76692710af038b90c69bf33d6d0f99476d + GIT_TAG c1f9ede366d02e3531ecbaec5e3769312f31cccd ) endfunction() From 33291b878e4c9ca4eacf488477c8d7790649df33 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Sun, 8 Jan 2023 21:53:26 -0800 Subject: [PATCH 08/12] Adding tutorial for Skinned Shape Deformation --- tutorial/tut-chapter7.ipynb | 532 ++++++++++++++++++++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 tutorial/tut-chapter7.ipynb diff --git a/tutorial/tut-chapter7.ipynb b/tutorial/tut-chapter7.ipynb new file mode 100644 index 00000000..e255bbb1 --- /dev/null +++ b/tutorial/tut-chapter7.ipynb @@ -0,0 +1,532 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Chapter 7: Skinned Shape Deformation\n", + "[![Anaconda-Server Badge](https://anaconda.org/conda-forge/igl/badges/installer/conda.svg)](https://conda.anaconda.org/conda-forge)\n", + "[![Anaconda-Server Badge](https://anaconda.org/conda-forge/igl/badges/downloads.svg)](https://anaconda.org/conda-forge/igl)\n", + "[![Anaconda-Server Badge](https://anaconda.org/conda-forge/igl/badges/platforms.svg)](https://anaconda.org/conda-forge/igl)\n", + "[![Anaconda-Server Badge](https://anaconda.org/conda-forge/igl/badges/latest_release_date.svg)](https://anaconda.org/conda-forge/igl)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Overview\n", + "\n", + "In computer animation, shape deformation is often referred to as “skinning”. Constraints are posed as relative rotations of internal rigid “bones” inside a character. The deformation method, or skinning method, determines how the surface of the character (i.e. its skin) should move as a function of the bone rotations.\n", + "\n", + "In this chapter, we show 3 techniques\n", + "1. Rigid Skinning - the most basic shape deformation technique\n", + "2. Linear Blend Skinning (LBS) - the most commonly used shape deformation technique\n", + "3. Direct Delta Mush (DDM) - one of many advanced deformation techniques to address limitations of LBS" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import igl\n", + "import scipy as sp\n", + "import numpy as np\n", + "import scripts.meshplot as mp\n", + "\n", + "import os\n", + "\n", + "root_folder = os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Mesh Loading\n", + "\n", + "Load the elephant mesh along with it's skeleton structure, the weights necessary to control the mesh using the skeleton, and an animation sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Array Shapes\n", + "Vertices: (6034, 3), Faces: (12064, 3), Bones: (25, 3), Parents: (24, 2), Weights: (6034, 24), Anim: (288, 457)\n" + ] + } + ], + "source": [ + "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"elephant.obj\"))\n", + "\n", + "bones, parents, _, _, _, _ = igl.read_tgf(os.path.join(root_folder, \"data\", \"elephant.tgf\"))\n", + "\n", + "w = igl.read_dmat(os.path.join(root_folder, \"data\", \"elephant-weights.dmat\"))\n", + "\n", + "anim = igl.read_dmat(os.path.join(root_folder, \"data\", \"elephant-anim.dmat\"))\n", + "\n", + "num_frames = anim.shape[1]\n", + "\n", + "print(f\"Array Shapes\\nVertices: {v.shape}, Faces: {f.shape}, Bones: {bones.shape}, Parents: {parents.shape}, Weights: {w.shape}, Anim: {anim.shape}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First line loads the elephant mesh which contains the vertices and faces as demonstrated in [chapter 0](https://github.com/libigl/libigl-python-bindings/blob/master/tutorial/tut-chapter0.ipynb)\n", + "\n", + "Second line loads a set of bones and a skeleton heirarchy described by parents.\n", + "\n", + "Third line loads a set of weights $W$ describing how much each vertex ($i$) will be influenced by the bones loaded above ($w_i$). These weights are meant to be used with Linear Blend Skinning.\n", + "\n", + "Fourth line loads an animation sequence where each column describes a pose $\\theta$. $\\theta$ is a stack of vectorized affine transforms, one for each bone describing the translation and rotation of the bone. In this elephant object example, there are a total of 24 affectable bones. Hence, each column has $3 * 4 * 24 = 288$ elements where the affine transform for each bone is $3$ rows and $4$ columns." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Rigid Skinning\n", + "\n", + "In the rigid (or simple) skinning approach, each vertex in the mesh is attached to exactly one bone in the skeleton. When the skeleton is posed, the vertices are transformed by their joint’s world space matrix. Every vertex $i$ is transformed by exactly one matrix using the equation $u_i = v_i . W$, where $W$ is the skinning weights matrix loaded earlier." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Example of Conversion at vertex 10\n", + "LBS Weights: [9.99997436e-01 0.00000000e+00 2.40198315e-06 0.00000000e+00]\n", + "Rigid Weights: [1. 0. 0. 0.]\n" + ] + } + ], + "source": [ + "def convert_lbs_weights_rigid_weights(lbs_w):\n", + " rigid_w = np.zeros(w.shape)\n", + " for i in range(0, w.shape[0]):\n", + " maxj = w[i].argmax()\n", + " for j in range(0, w.shape[1]):\n", + " rigid_w[i, j] = float(maxj == j)\n", + " return rigid_w\n", + " \n", + "rigid_w = convert_lbs_weights_rigid_weights(w)\n", + " \n", + "rigid_lbs_matrix = igl.lbs_matrix(v, rigid_w)\n", + " \n", + "print(f\"Example of Conversion at vertex 10\\nLBS Weights: {w[10, 5:9]}\\nRigid Weights: {rigid_w[10, 5:9]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `convert_lbs_weights_rigid_weights` is just a helper function to help us convert the LBS weights we loaded into rigid skinning weights. For rigid skinning, each vertex can be inluenced by one bone. So all we do is find the maximum influence for each vertex and set the weight there to 1 and the rest to 0.\n", + "\n", + "The output shows an example of how bones $5, 6, 7$ and $8$ affect vertex $10$ through LBS and Rigid skinning. A value of $1.0$ implies that the respective bone fully controls the vertex and a value of $0.0$ implies that the bone has no inluence on the vertex.\n", + "\n", + "Finally, we use `igl.lbs_matrix` function to produce matrix $M$ as described in the [Fast Automatic Skinning Transformations tutorial](https://libigl.github.io/tutorial/#fast-automatic-skinning-transformations). " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ec240c69cb66464398ec7b3b2199073f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c56a26105d19475997cd08e415e277d7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529960…" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def rigid_deform_mesh(pose):\n", + " rigid_v = rigid_lbs_matrix @ pose\n", + " return rigid_v\n", + "\n", + "viewer_rigid = mp.Viewer({})\n", + "rigid = viewer_rigid.add_mesh(v, f, np.array([0.0, 0.5, 0.0]))\n", + "\n", + "@mp.interact(frame=(1, num_frames))\n", + "def update_frame(frame):\n", + " frame = frame - 1\n", + " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", + " deformed_mesh = rigid_deform_mesh(pose)\n", + " \n", + " viewer_rigid.update_object(oid=rigid, vertices=deformed_mesh)\n", + " \n", + "viewer_rigid._renderer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `rigid_deform_mesh` just multiplies the matrix $M$ with the pose $\\theta$ to produce the deformed mesh." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Linear Blend Skinning\n", + "\n", + "In the linear blend skinning approach, each vertex in the mesh can be affected by one or more bones in the skeleton. When the skeleton is posed, the vertices are transformed by doing a weighted sum of joints' world space matrices. The influence of a bone on a vertex can be weighted between $0.0$ and $1.0$ and the sum of influences of bones on each vertex should sum to 1. That is, $\\sum_{j=1}^{J} w_i = 1.0, \\forall i \\in V$ where $V$ is the set of vertices and $J$ is the number of bones." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7c878a5d35124479b4ae2e96cd31149b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "45d4a8b34cfd4d1c92a25922d416941b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529960…" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lbs_matrix = igl.lbs_matrix(v, w)\n", + "\n", + "def lbs_deform_mesh(pose):\n", + " lbs_v = lbs_matrix @ pose\n", + " return lbs_v\n", + "\n", + "viewer_lbs = mp.Viewer({})\n", + "lbs = viewer_lbs.add_mesh(v, f, np.array([0.5, 0.0, 0.0]))\n", + "\n", + "@mp.interact(frame=(1, num_frames))\n", + "def update_frame(frame):\n", + " frame = frame - 1\n", + " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", + " deformed_mesh = lbs_deform_mesh(pose)\n", + " \n", + " viewer_lbs.update_object(oid=lbs, vertices=deformed_mesh)\n", + " \n", + "viewer_lbs._renderer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `lbs_deform_mesh` just multiplies the matrix $M$ with the pose $\\theta$ to produce the deformed mesh similar to `rigid_deform_mesh`. The key difference being that `lbs_deform_mesh` uses the matrix $M$ produced by the originally loaded LBS weights $W$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Direct Delta Mush Skinning\n", + "\n", + "Linear blend skinning suffers from shrinkage and collapse artifacts due to its inherent linearity. Direct Delta Mush skinning attempts to solve both of these issues by providing a direct skinning method that takes as input a rig with piecewise-constant weight functions (weights are either $=0$ or $=1$ everywhere, i.e weights used for rigid skinning above). Direct delta mush is an adaptation of a less performant method called simply **Delta Mush**. The computation of Delta Mush separates into **“bind pose” precomputation** and **runtime evaluation**.\n", + "\n", + "## \"Bind Pose\" Precomputation\n", + "\n", + "At bind time, Laplacian smoothing is conducted on the bind pose, moving each vertex from its rest position $v_i$ to a new position $\\tilde{v_i}$. The “delta” describing undoing this smoothing procedure, is computed and stored in a local coordinate frame associated with the vertex:\n", + "\n", + "$\\delta_i = T_i^{−1}(v_i − \\tilde{v_i})$\n", + "\n", + "The result is “vector-valued” skinning weights per-vertex per-bone. This can be stored in a matrix $\\Omega$ (i.e `omega`)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Num Frames: 457, Omega: (6034, 240)\n" + ] + } + ], + "source": [ + "p = 20\n", + "l = 3\n", + "k = 1\n", + "a = 0.8\n", + "omega = igl.direct_delta_mush_precomputation(v, f, rigid_w, p, l, k, a)\n", + "\n", + "print(f\"Num Frames: {num_frames}, Omega: {omega.shape}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `igl.direct_delta_mush_precomputation` function generates $\\Omega$ that has 10 skinning weights per vertex per bone provided the rest pose vertices, faces, piecewise-constant weights (here we use rigid skinning weight `rigid_w`) and a set of smoothness control parameters. For this example that is 10 weights for each of the 24 bones of the elephant's skeleton for each of 6034 vertices of the mesh.\n", + "\n", + "The smoothness can be controlled through parameters \n", + "- `p` ($ > 0$)\n", + "- `l` or $\\lambda$ ($> 0$) : \n", + "-`k` or $\\kappa$ ($> 0 and < \\lambda$):\n", + "- 'a' or $\\alpha$ ($> 0 and < 1$):\n", + "\n", + "Here, `p` is the number of iterations. The values here were used from [Example 408](https://github.com/libigl/libigl/blob/main/tutorial/408_DirectDeltaMush/main.cpp) of the libigl C++ tutorials.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Direct Delta Mush Runtime Evaluation\n", + "\n", + "At runtime, $\\Omega$ is used to deform the mesh to its final locations. The mesh is deformed using linear blend skinning and piecewise-constant weights. Near bones, the deformation is perfectly rigid, while near joints where bones meet, the mesh tears apart with a sudden change to the next rigid transformation. A local frame $S_i$ is computed at this location and the cached deltas are added in this resolved frame to restore the shape’s original details:\n", + "\n", + "$u_i = \\tilde{u_i} + S_i . \\delta_i$\n", + "\n", + "The key insight of “Delta Mush” is that Laplacian smoothing acts similarly on the rest and posed models." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8e8f2a83a8764eb68fc250a1123655d9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6a7db55685ac42b8b6e6fa68c3680904", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529960…" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def ddm_deform_mesh(pose):\n", + " ddm_v = igl.direct_delta_mush(v.copy(), pose, omega)\n", + " return ddm_v\n", + "\n", + "viewer_ddm = mp.Viewer({})\n", + "ddm = viewer_ddm.add_mesh(v, f, np.array([0.0, 0.5, 0.5]))\n", + "\n", + "@mp.interact(frame=(1, num_frames))\n", + "def update_frame(frame):\n", + " frame = frame - 1\n", + " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", + " deformed_mesh = ddm_deform_mesh(pose)\n", + " \n", + " viewer_ddm.update_object(oid=ddm, vertices=deformed_mesh)\n", + " \n", + "viewer_ddm._renderer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `igl.direct_delta_mush` consumes the rest pose vertices `v`, the pose $\\theta$, and the matrix $\\Omega$ we precomputed above to deform the mesh. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comparison between the Skinning Techniques" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b40a4a7a4b544f3686f1dc3d38d3e338", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7d41cad22ae54e5cb01bdb132e1267a6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529998…" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "viewer_comp = mp.Viewer({})\n", + "# Rigid Blue\n", + "offset_rigid = np.array([-90.0, 0.0, 0.0])\n", + "rigid = viewer_comp.add_mesh(v + offset_rigid, f, np.array([0.0, 0.5, 0.0]))\n", + "\n", + "# LBS Red\n", + "offset_lbs = np.array([0.0, 0.0, 0.0])\n", + "lbs = viewer_comp.add_mesh(v + offset_lbs, f, np.array([0.5, 0.0, 0.0]))\n", + "\n", + "# DDM Green\n", + "offset_ddm = np.array([90.0, 0.0, 0.0])\n", + "ddm = viewer_comp.add_mesh(v + offset_ddm, f, np.array([0.0, 0.5, 0.5]))\n", + "\n", + "\n", + "@mp.interact(frame=(1, num_frames))\n", + "def update_frame(frame):\n", + " frame = frame - 1\n", + " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", + " rigid_deformed_mesh = rigid_deform_mesh(pose)\n", + " lbs_deformed_mesh = lbs_deform_mesh(pose)\n", + " ddm_deformed_mesh = ddm_deform_mesh(pose)\n", + " \n", + " viewer_comp.update_object(oid=rigid, vertices=rigid_deformed_mesh + offset_rigid)\n", + " viewer_comp.update_object(oid=lbs, vertices=lbs_deformed_mesh + offset_lbs)\n", + " viewer_comp.update_object(oid=ddm, vertices=ddm_deformed_mesh + offset_ddm)\n", + " \n", + "viewer_comp._renderer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The visualization shows a comparison between the 3 techniques\n", + "1. Rigid Skinning: Green.\n", + "2. Linear Blend Skinning: Red.\n", + "3. Direct Delta Mush Skinning: Teal.\n", + "\n", + "Frame 181 presents a good case where rigid skinning fails with a ton of artificats, where as linear blend skinning does slightly better. LBS, however, ends up with volume loss at the limbs and Direct Delta Mush skinning does a good job of cleaning this up." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# References\n", + "\n", + "- *UCSD Reading (https://cseweb.ucsd.edu/classes/sp16/cse169-a/readings/3-Skin.html)*\n", + "- *Binh Huy Le, J.P. Lewis. [Direct delta mush skinning and variants](https://binh.graphics/papers/2019s-DDM/Direct_Delta_Mush_and_Variants.pdf), 2019*\n", + "- *Joe Mancewicz, Matt L. Derksen, Hans Rijpkema, and Cyrus A. Wilson. [Delta Mush: smoothing deformations while preserving detail](https://dl.acm.org/doi/10.1145/2633374.2633376), 2014.*\n", + "- *Alec Jacobson, Ilya Baran, Ladislav Kavan, Jovan Popović, and Olga Sorkine. [Fast Automatic Skinning Transformations](https://igl.ethz.ch/projects/fast/fast-automatic-skinning-transformations-siggraph-2012-jacobson-et-al.pdf), 2012*" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + }, + "vscode": { + "interpreter": { + "hash": "ce7907e81cf2b9da16cd02e98dafe35a67eae35daae8f394751b5721196d6988" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From e284244d89955ff5a9eaae2dcff105b31f454f65 Mon Sep 17 00:00:00 2001 From: Zhongshi Date: Fri, 6 Jan 2023 14:55:21 -0500 Subject: [PATCH 09/12] fix continuous integration with numpy types (#154) * Update README.md * deprecate np.float * deprecate np.int * updated deprecated package in setup * fixed types in tests * added default dtype Co-authored-by: Teseo Schneider --- README.md | 1 - azure-pipelines.yml | 1 + setup.py | 6 ++-- tests/test_basic.py | 72 ++++++++++++++++++++++++--------------------- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 69910e1c..aafab89f 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,3 @@ To run the tests: python setup.py test ``` - diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 35adb906..13fe0f6e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,6 +51,7 @@ jobs: python -m pip install --upgrade pip pip install numpy pip install scipy + pip install packaging displayName: 'Install dependencies' - script: | diff --git a/setup.py b/setup.py index 13bf0200..25313233 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import platform import subprocess -from distutils.version import LooseVersion +from packaging.version import Version from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext @@ -24,8 +24,8 @@ def run(self): # self.debug = True - cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) - if cmake_version < '3.2.0': + cmake_version = Version(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) + if cmake_version < Version('3.2.0'): raise RuntimeError("CMake >= 3.2.0 is required") for ext in self.extensions: diff --git a/tests/test_basic.py b/tests/test_basic.py index d19505c3..1f427b5b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -5,7 +5,7 @@ import igl import numpy as np import scipy as sp -import scipy.sparse.csc as csc +import scipy.sparse as csc import math @@ -32,6 +32,9 @@ def setUp(self): self.f = np.random.randint(0, 10, size=(20, 3), dtype=self.f1.dtype) self.g = np.random.randint(0, 10, size=(20, 4), dtype="int32") + self.default_int = np.array(range(2)).dtype + self.default_float = np.zeros((2,2)).dtype + def tearDown(self): vv1, ff1 = igl.read_triangle_mesh( os.path.join(self.test_path, "bunny_small.off")) @@ -140,8 +143,8 @@ def test_massmatrix(self): self.v, self.f, type=igl.MASSMATRIX_TYPE_BARYCENTRIC) self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0])) self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.dtype == np.float64) - self.assertTrue(a.dtype == np.float64) + self.assertTrue(b.dtype == self.v.dtype) + self.assertTrue(a.dtype == self.v.dtype) self.assertTrue(type(a) == type(b) == csc.csc_matrix) def test_massmatrix_intrinsic(self): @@ -151,8 +154,8 @@ def test_massmatrix_intrinsic(self): el, self.f, type=igl.MASSMATRIX_TYPE_BARYCENTRIC) self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0])) self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.dtype == np.float64) - self.assertTrue(a.dtype == np.float64) + self.assertTrue(b.dtype == el.dtype) + self.assertTrue(a.dtype == el.dtype) self.assertTrue(type(a) == type(b) == csc.csc_matrix) def test_principal_curvature(self): @@ -164,12 +167,12 @@ def test_principal_curvature(self): self.assertTrue(pv1.shape == qv1.shape == pv2.shape == qv2.shape == (self.v.shape[0],)) self.assertTrue(pd1.dtype == pd2.dtype == - pv1.dtype == pv2.dtype == np.float64) + pv1.dtype == pv2.dtype == self.v.dtype) v = self.v.copy() pd1, pd2, pv1, pv2 = igl.principal_curvature(v, self.f) self.assertTrue(pd1.dtype == pd2.dtype == - pv1.dtype == pv2.dtype == np.float64) + pv1.dtype == pv2.dtype == v.dtype) self.assertTrue(type(pd1) == type(pd2) == type(pv1) == type(pv2) == np.ndarray) self.assertTrue(pd1.flags.c_contiguous) @@ -186,7 +189,7 @@ def test_read_obj(self): self.assertTrue(type(v) == type(f) == type(n) == np.ndarray) self.assertTrue(v.shape == (25905, 3) and n.shape == (0, 0) and f.shape == (51712, 3)) - self.assertTrue(v.dtype == np.float64) + self.assertTrue(v.dtype == self.default_float) self.assertTrue(f.dtype == self.f.dtype) v, _, n, f, _, _ = igl.read_obj( self.test_path + "face.obj", dtype="float32") @@ -202,7 +205,7 @@ def test_read_off(self): self.assertTrue(type(v) == type(f) == type(n) == np.ndarray) self.assertTrue(v.shape == (3485, 3) and n.shape == (0, 0) and f.shape == (6966, 3)) - self.assertTrue(v.dtype == np.float64) + self.assertTrue(v.dtype == self.default_float) v, f, n = igl.read_off( self.test_path + "bunny_small.off", read_normals=False, dtype="float32") self.assertTrue(v.shape == (3485, 3) and n.shape == @@ -220,7 +223,7 @@ def test_read_mesh(self): self.assertTrue(t.flags.c_contiguous) self.assertTrue(f.flags.c_contiguous) - self.assertTrue(v.dtype == np.float64) + self.assertTrue(v.dtype == self.default_float) self.assertTrue(t.dtype == self.f1.dtype) self.assertTrue(f.dtype == self.f1.dtype) @@ -751,12 +754,12 @@ def test_marching_cubes(self): self.assertEqual(V.shape, (0, 3)) self.assertEqual(F.shape, (0, 3)) - #test marching over a sphere - sphereField = np.linalg.norm(pts, axis=1) - 1 + #test marching over a sphere + sphereField = np.linalg.norm(pts, axis=1) - 1 V,F = igl.marching_cubes(sphereField, pts, n, n, n, 0.0) self.assertTrue(V.dtype == pts.dtype) - self.assertTrue(F.dtype == np.int) + self.assertTrue(F.dtype == self.default_int) self.assertNotEqual(V.shape, (0,3)) self.assertNotEqual(F.shape, (0,3)) @@ -1233,7 +1236,7 @@ def test_signed_distance(self): self.assertTrue(i.flags.c_contiguous) self.assertTrue(c.flags.c_contiguous) - self.assertTrue(s.dtype == np.float64) + self.assertTrue(s.dtype == self.v1.dtype) self.assertTrue(c.dtype == self.v1.dtype) self.assertTrue(i.dtype == self.f1.dtype) @@ -1242,7 +1245,7 @@ def test_signed_distance(self): p, self.v1, self.f1, return_normals=True) self.assertEqual(n.shape, p.shape) self.assertTrue(n.flags.c_contiguous) - self.assertTrue(n.dtype == np.float64) + self.assertTrue(n.dtype == self.v1.dtype) # ensure error raised when trying param other than pseudonormal for normals with self.assertRaises(ValueError): @@ -1750,15 +1753,16 @@ def test_edges_to_path(self): self.assertTrue(k.dtype == e.dtype) def test_path_to_edges(self): - e1 = igl.path_to_edges(np.array(range(20)), False) - e2 = igl.path_to_edges(np.array(range(20)), True) - r2 = np.vstack([np.array(range(20)), np.array(range(1, 21))]).T + v_indices = np.array(range(20)) + e1 = igl.path_to_edges(v_indices, False) + e2 = igl.path_to_edges(v_indices, True) + r2 = np.vstack([v_indices, np.array(range(1, 21))]).T r2[19, 1] = 0 self.assertTrue(np.allclose(e2, r2)) self.assertTrue(e1.flags.c_contiguous) self.assertTrue(e2.flags.c_contiguous) - self.assertTrue(e1.dtype == np.int) - self.assertTrue(e2.dtype == np.int) + self.assertTrue(e1.dtype == v_indices.dtype) + self.assertTrue(e2.dtype == v_indices.dtype) def test_exterior_edges(self): e = igl.exterior_edges(self.f1) @@ -2130,7 +2134,7 @@ def test_read_msh(self): self.assertTrue(v.flags.c_contiguous) self.assertTrue(t.flags.c_contiguous) - self.assertTrue(v.dtype == np.float64) + self.assertTrue(v.dtype == self.default_float) self.assertTrue(t.dtype == self.f1.dtype) def test_two_axis_valuator_fixed_up(self): @@ -2320,9 +2324,9 @@ def test_quad_grid(self): self.assertTrue(q.shape == (2*2, 4)) self.assertTrue(v.flags.c_contiguous) self.assertTrue(q.flags.c_contiguous) - self.assertTrue(v.dtype == np.float) - self.assertTrue(q.dtype == np.int) - self.assertTrue(e.dtype == np.int) + self.assertTrue(v.dtype == self.default_float) + self.assertTrue(q.dtype == self.default_int) + self.assertTrue(e.dtype == self.default_int) def test_sparse_voxel_grid(self): def sphere1(point): @@ -2330,10 +2334,10 @@ def sphere1(point): point = np.array([1.0, 0.0, 0.0]) cs, cv, ci = igl.sparse_voxel_grid(point, sphere1, 1.0, 100) self.assertTrue(cv.flags.c_contiguous) - self.assertTrue(cv.dtype == np.float) + self.assertTrue(cv.dtype == self.default_float) self.assertTrue(cv.shape == (len(cs), 3)) self.assertTrue(ci.flags.c_contiguous) - self.assertTrue(ci.dtype == np.int) + self.assertTrue(ci.dtype == self.default_int) self.assertTrue(ci.shape[1] == 8) def test_topological_hole_fill(self): @@ -2343,7 +2347,7 @@ def test_topological_hole_fill(self): ff = igl.topological_hole_fill(f, b, h) self.assertTrue(ff.flags.c_contiguous) self.assertTrue(ff.shape[1] == 3) - self.assertTrue(ff.dtype == np.int) + self.assertTrue(ff.dtype == f.dtype) self.assertTrue(ff.shape[0] != f.shape[0]) def test_triangulated_grid(self): @@ -2352,8 +2356,8 @@ def test_triangulated_grid(self): self.assertTrue(f.shape == (162, 3)) self.assertTrue(f.flags.c_contiguous) self.assertTrue(v.flags.c_contiguous) - self.assertTrue(v.dtype == np.float) - self.assertTrue(f.dtype == np.int) + self.assertTrue(v.dtype == self.default_float) + self.assertTrue(f.dtype == self.default_int) def test_unproject_on_line(self): pos = np.array([10., 10.]) @@ -2365,7 +2369,7 @@ def test_unproject_on_line(self): self.assertTrue(z.flags.c_contiguous) self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == np.float) + self.assertTrue(z.dtype == pos.dtype) def test_unproject_on_plane(self): pos = np.array([10., 10.]) @@ -2376,7 +2380,7 @@ def test_unproject_on_plane(self): self.assertTrue(z.flags.c_contiguous) self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == np.float) + self.assertTrue(z.dtype == pos.dtype) def test_fast_winding_number_for_points(self): xs = np.linspace(-5.0, 5.0, 10) @@ -2388,7 +2392,7 @@ def test_fast_winding_number_for_points(self): wn = igl.fast_winding_number_for_points(self.v1, n, a, grid) self.assertTrue(wn.flags.c_contiguous) self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == np.float) + self.assertTrue(wn.dtype == self.v1.dtype) def test_fast_winding_number_for_meshes(self): xs = np.linspace(-5.0, 5.0, 10) @@ -2398,7 +2402,7 @@ def test_fast_winding_number_for_meshes(self): wn = igl.fast_winding_number_for_meshes(self.v1, self.f1, grid) self.assertTrue(wn.flags.c_contiguous) self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == np.float) + self.assertTrue(wn.dtype == self.v1.dtype) def test_flip_avoiding_line_search(self): def fun(v): @@ -2420,7 +2424,7 @@ def test_edge_flaps(self): self.assertTrue(ef.flags.c_contiguous) self.assertTrue(ei.flags.c_contiguous) self.assertTrue(e.dtype == emap.dtype == - ef.dtype == ei.dtype == np.int) + ef.dtype == ei.dtype == self.f2.dtype) def test_circulation(self): pass From b23a4d23e20337f2d747fa1628941ed4af3c3784 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Tue, 10 Jan 2023 20:03:21 -0800 Subject: [PATCH 10/12] Remove unnecessary copies of arguments --- src/direct_delta_mush.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/direct_delta_mush.cpp b/src/direct_delta_mush.cpp index 2c0d7930..74b35f20 100644 --- a/src/direct_delta_mush.cpp +++ b/src/direct_delta_mush.cpp @@ -11,7 +11,6 @@ Computes Direct Delta Mesh Skinning (Variant 0) from Parameters ---------- v #V by 3 list of rest pose vertex positions -e #E Number of bones t #E*4 by 3 list of bone pose transformations omega #V by #E*10 list of precomputated matrix values @@ -43,21 +42,17 @@ npe_begin_code() assert_valid_bone_transforms(t, "t"); assert_rows_equals(t, (omega.cols() * 4) / 10, "t"); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXd t_copy = t.template cast(); - Eigen::MatrixXd omega_copy = omega.template cast(); - std::vector> - t_affine(t_copy.rows() / 4); + t_affine(t.rows() / 4); for(int bone = 0; bone < t_affine.size(); ++bone) { t_affine[bone] = Eigen::Affine3d::Identity(); - t_affine[bone].matrix().block(0, 0, 3, 4) = t_copy.block(bone * 4, 0, 4, 3).transpose(); + t_affine[bone].matrix().block(0, 0, 3, 4) = t.block(bone * 4, 0, 4, 3).transpose(); } EigenDenseLike u; - igl::direct_delta_mush(v_copy, t_affine, omega_copy, u); + igl::direct_delta_mush(v, t_affine, omega, u); return npe::move(u); npe_end_code() @@ -105,12 +100,11 @@ npe_arg(alpha, double) npe_begin_code() assert_valid_3d_tri_mesh(v, f); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXd w_copy = w.template cast(); Eigen::MatrixXi f_copy = f.template cast(); + Eigen::MatrixXd w_copy = w.template cast(); EigenDenseLike omega; - igl::direct_delta_mush_precomputation(v_copy, f_copy, w_copy, p, lambda, kappa, alpha, omega); + igl::direct_delta_mush_precomputation(v, f_copy, w_copy, p, lambda, kappa, alpha, omega); return npe::move(omega); npe_end_code() From 5d1b2d04fdb63427fb924894ed7e2ffb6172f057 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Tue, 10 Jan 2023 20:14:37 -0800 Subject: [PATCH 11/12] add dtype check for omega --- tests/test_basic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 1f427b5b..32e67bd5 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1546,6 +1546,7 @@ def test_direct_delta_mush(self): self.assertTrue(omega.shape[0] == V.shape[0]) self.assertTrue(omega.shape[1] == BE.shape[0] * 10) + self.assertTrue(omega.dtype == np.double) T = np.zeros((BE.shape[0]*4, 3)) I = np.eye(3) @@ -1556,7 +1557,7 @@ def test_direct_delta_mush(self): self.assertTrue(U.shape[0] == V.shape[0]) self.assertTrue(U.shape[1] == 3) - self.assertTrue(U.dtype == V.dtype) + self.assertTrue(U.dtype == np.double) self.assertFalse(np.isnan(U).any()) def test_direct_delta_mush_precomputation(self): From f69815acd1a343bad066fed7f1c6be4fea587d04 Mon Sep 17 00:00:00 2001 From: Kishore Venkateshan Date: Tue, 10 Jan 2023 20:23:34 -0800 Subject: [PATCH 12/12] Remove unnecessary cast on faces as well --- src/direct_delta_mush.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/direct_delta_mush.cpp b/src/direct_delta_mush.cpp index 74b35f20..6ee03c29 100644 --- a/src/direct_delta_mush.cpp +++ b/src/direct_delta_mush.cpp @@ -100,11 +100,10 @@ npe_arg(alpha, double) npe_begin_code() assert_valid_3d_tri_mesh(v, f); - Eigen::MatrixXi f_copy = f.template cast(); Eigen::MatrixXd w_copy = w.template cast(); EigenDenseLike omega; - igl::direct_delta_mush_precomputation(v, f_copy, w_copy, p, lambda, kappa, alpha, omega); + igl::direct_delta_mush_precomputation(v, f, w_copy, p, lambda, kappa, alpha, omega); return npe::move(omega); npe_end_code()