Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ option(LIBIGL_CYCODEBASE "Build igl::cycodebase bindings" ON)
FetchContent_Declare(
libigl
GIT_REPOSITORY https://github.com/libigl/libigl.git
GIT_TAG 72217e00bae6a7e041221e187aafbdc793f905d2
GIT_TAG 0cbcaf886314ff331402c38fbb04cc4de8d7c033
)
FetchContent_MakeAvailable(libigl)

Expand Down
1 change: 1 addition & 0 deletions include/default_types.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma once
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <cstdint>
Expand Down
45 changes: 45 additions & 0 deletions include/parse_transforms.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once
#include "default_types.h"
#include <Eigen/Geometry>
#include <stdexcept>
#include <string>
#include <vector>

namespace pyigl
{
typedef Eigen::Transform<Numeric,3,Eigen::Affine> AffineN3;
typedef std::vector<AffineN3,Eigen::aligned_allocator<AffineN3> > AffineN3List;

/// Convert a Python list of 4脳4 (or 3脳4) matrices into the list of
/// Eigen::Transforms that the igl::swept_volume* functions expect.
///
/// @param[in] transforms #transforms list of 4脳4 or 3脳4 matrices
/// @param[in] caller name used to prefix error messages
/// @return #transforms list of affine transformations
inline AffineN3List parse_transforms(
const std::vector<Eigen::MatrixXN> &transforms,
const char * const caller)
{
if(transforms.empty())
{
throw std::runtime_error(
std::string(caller)+": transforms must be non-empty");
}
AffineN3List T;
T.reserve(transforms.size());
for(const auto & M : transforms)
{
if(M.cols() != 4 || (M.rows() != 3 && M.rows() != 4))
{
throw std::runtime_error(
std::string(caller)+": each transform must be 3脳4 or 4脳4");
}
AffineN3 Ti;
// A 3脳4 input leaves the implicit bottom row as [0 0 0 1]
Ti.matrix().setIdentity();
Ti.matrix().topRows(M.rows()) = M;
T.emplace_back(Ti);
}
return T;
}
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ build-backend = "scikit_build_core.build"

[project]
name = "libigl"
version = "2.6.3.dev4"
version = "2.6.3.dev5"
description = "libigl: A simple C++ geometry processing library"
readme = "README.md"
requires-python = ">=3.8"
Expand Down
60 changes: 60 additions & 0 deletions src/swept_volume.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "default_types.h"
#include "parse_transforms.h"
#include <igl/swept_volume.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/tuple.h>
#include <nanobind/stl/vector.h>
#include <vector>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
auto swept_volume(
const nb::DRef<const Eigen::MatrixXN> &V,
const nb::DRef<const Eigen::MatrixXI> &F,
/* Each transform is tiny; let nanobind copy them */
const std::vector<Eigen::MatrixXN> &transforms,
const igl::SignedDistanceType sign_type,
const Integer grid_res,
const Numeric isolevel)
{
const AffineN3List T = parse_transforms(transforms,"swept_volume");
Eigen::MatrixXN SV;
Eigen::MatrixXI SF;
igl::swept_volume(V,F,T,sign_type,grid_res,isolevel,SV,SF);
return std::make_tuple(SV,SF);
}
}

// Bind the wrapper to the Python module
void bind_swept_volume(nb::module_ &m)
{
m.def(
"swept_volume",
&pyigl::swept_volume,
"V"_a,
"F"_a,
"transforms"_a,
"sign_type"_a = igl::SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER,
"grid_res"_a = 50,
"isolevel"_a = 0,
R"(Compute the surface of the swept volume of a solid object with surface
(V,F) mesh under going rigid motion.

@param[in] V #V by 3 list of mesh positions in reference pose
@param[in] F #F by 3 list of mesh indices into rows of V
@param[in] transforms #transforms list of rigid transformations, one per time
step, each given as a 4脳4 (or 3脳4) matrix
@param[in] sign_type method for computing distance _sign_
@param[in] grid_res number of grid cells on the longest side containing the
motion (enough cells to cover isolevel, plus one, will also be added on
each side as padding)
@param[in] isolevel distance level to be contoured as swept volume (in the
same units as V)
@return Tuple containing:
- SV: #SV by 3 list of mesh positions of the swept surface
- SF: #SF by 3 list of mesh faces into rows of SV)");
}
48 changes: 48 additions & 0 deletions src/swept_volume_bounding_box.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "default_types.h"
#include "parse_transforms.h"
#include <igl/swept_volume_bounding_box.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/tuple.h>
#include <nanobind/stl/vector.h>
#include <Eigen/Geometry>
#include <vector>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
auto swept_volume_bounding_box(
const nb::DRef<const Eigen::MatrixXN> &V,
/* Each transform is tiny; let nanobind copy them */
const std::vector<Eigen::MatrixXN> &transforms)
{
const AffineN3List T =
parse_transforms(transforms,"swept_volume_bounding_box");
Eigen::AlignedBox<Numeric,3> box;
igl::swept_volume_bounding_box(V,T,box);
const Eigen::VectorXN min_corner = box.min();
const Eigen::VectorXN max_corner = box.max();
return std::make_tuple(min_corner,max_corner);
}
}

// Bind the wrapper to the Python module
void bind_swept_volume_bounding_box(nb::module_ &m)
{
m.def(
"swept_volume_bounding_box",
&pyigl::swept_volume_bounding_box,
"V"_a,
"transforms"_a,
R"(Construct an axis-aligned bounding box containing a shape undergoing a
motion sampled at a list of discrete rigid transformations.

@param[in] V #V by 3 list of mesh positions in reference pose
@param[in] transforms #transforms list of rigid transformations, one per time
step, each given as a 4脳4 (or 3脳4) matrix
@return Tuple containing:
- min_corner: 3-vector of the minimum corner of the box
- max_corner: 3-vector of the maximum corner of the box)");
}
91 changes: 91 additions & 0 deletions src/swept_volume_signed_distance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include "default_types.h"
#include "parse_transforms.h"
#include <igl/swept_volume_signed_distance.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/vector.h>
#include <limits>
#include <stdexcept>
#include <vector>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
auto swept_volume_signed_distance(
const nb::DRef<const Eigen::MatrixXN> &V,
const nb::DRef<const Eigen::MatrixXI> &F,
/* Each transform is tiny; let nanobind copy them */
const std::vector<Eigen::MatrixXN> &transforms,
const igl::SignedDistanceType sign_type,
const nb::DRef<const Eigen::MatrixXN> &GV,
const nb::DRef<const Eigen::VectorXI> &res,
const Numeric h,
const Numeric isolevel,
const nb::DRef<const Eigen::VectorXN> &S0)
{
const AffineN3List T =
parse_transforms(transforms,"swept_volume_signed_distance");
if(res.size() != 3)
{
throw std::runtime_error(
"swept_volume_signed_distance: res must be a 3-vector");
}
if(res(0)*res(1)*res(2) != GV.rows())
{
throw std::runtime_error(
"swept_volume_signed_distance: res(0)*res(1)*res(2) must equal GV.rows()");
}
const Eigen::VectorXI r = res;
Eigen::VectorXN S;
if(S0.size() == 0)
{
igl::swept_volume_signed_distance(V,F,T,sign_type,GV,r,h,isolevel,S);
}else
{
if(S0.size() != GV.rows())
{
throw std::runtime_error(
"swept_volume_signed_distance: S0 must have GV.rows() entries");
}
igl::swept_volume_signed_distance(V,F,T,sign_type,GV,r,h,isolevel,S0,S);
}
return S;
}
}

// Bind the wrapper to the Python module
void bind_swept_volume_signed_distance(nb::module_ &m)
{
m.def(
"swept_volume_signed_distance",
&pyigl::swept_volume_signed_distance,
"V"_a,
"F"_a,
"transforms"_a,
"sign_type"_a = igl::SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER,
"GV"_a,
"res"_a,
"h"_a,
"isolevel"_a = std::numeric_limits<Numeric>::infinity(),
"S0"_a = Eigen::VectorXN(),
R"(Compute the signed distance to a sweep surface of a mesh under-going a
rigid motion discretely sampled at a list of transformations at a grid.

@param[in] V #V by 3 list of mesh positions in reference pose
@param[in] F #F by 3 list of triangle indices [0,n)
@param[in] transforms #transforms list of rigid transformations, one per time
step, each given as a 4脳4 (or 3脳4) matrix
@param[in] sign_type method for computing distance _sign_
@param[in] GV #GV by 3 list of evaluation point grid positions
@param[in] res 3-long resolution of the GV grid
@param[in] h edge-length of grid
@param[in] isolevel isolevel to "focus" on; grid positions far enough away
from isolevel (based on h) will get approximate values. Set
isolevel=inf (the default) to get good values everywhere (slow and
unnecessary if just trying to extract the isolevel-level set).
@param[in] S0 #GV list of initial values (the minimum with these is taken);
empty (the default) to start from scratch
@return S #GV list of signed distances)");
}
39 changes: 39 additions & 0 deletions src/voxel_grid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/tuple.h>
#include <Eigen/Geometry>
#include <stdexcept>

namespace nb = nanobind;
using namespace nb::literals;
Expand All @@ -20,6 +22,28 @@ namespace pyigl
igl::voxel_grid(V, offset, s, pad_count, GV, side);
return std::make_tuple(GV, side);
}

auto voxel_grid_box(
const nb::DRef<const Eigen::VectorXN> &min_corner,
const nb::DRef<const Eigen::VectorXN> &max_corner,
const int s,
const int pad_count)
{
if(min_corner.size() != 3 || max_corner.size() != 3)
{
throw std::runtime_error(
"voxel_grid: min_corner and max_corner must be 3-vectors");
}
// Named locals: `AlignedBox box(Matrix(a),Matrix(b));` would parse as a
// function declaration
const Eigen::Matrix<Numeric,3,1> mn = min_corner;
const Eigen::Matrix<Numeric,3,1> mx = max_corner;
const Eigen::AlignedBox<Numeric,3> box(mn,mx);
Eigen::MatrixXN GV;
Eigen::VectorXI side;
igl::voxel_grid(box, s, pad_count, GV, side);
return std::make_tuple(GV, side);
}
}

// Bind the wrappers to the Python module
Expand All @@ -39,4 +63,19 @@ void bind_voxel_grid(nb::module_ &m)
@param[in] s Number of cell centers on the largest side
@param[in] pad_count Number of cells beyond the box
@return Tuple (GV, side) where GV contains cell center positions and side defines grid dimensions)");
m.def(
"voxel_grid",
&pyigl::voxel_grid_box,
"min_corner"_a,
"max_corner"_a,
"s"_a,
"pad_count"_a=0,
R"(Construct the cell center positions of a regular voxel grid (lattice)
made of perfectly square voxels enclosing a given axis-aligned box.

@param[in] min_corner 3-vector of the minimum corner of the box to enclose
@param[in] max_corner 3-vector of the maximum corner of the box to enclose
@param[in] s Number of cell centers on the largest side (including 2*pad_count)
@param[in] pad_count Number of cells beyond the box
@return Tuple (GV, side) where GV contains cell center positions and side defines grid dimensions)");
}
Loading
Loading