Skip to content

Commit

Permalink
add WarpBatch - array process warp with single callback
Browse files Browse the repository at this point in the history
  • Loading branch information
wrongbad committed Dec 14, 2023
1 parent cc4966f commit 77b5cd8
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 78 deletions.
88 changes: 86 additions & 2 deletions bindings/python/manifold3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,63 @@ struct nanobind::detail::type_caster<glm::vec3> {

namespace nb = nanobind;

// helper to convert std::vector<glm::vec*> to numpy
template <class T, int N, glm::qualifier Precision>
nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>> to_numpy(
glm::vec<N, T, Precision> const *begin,
glm::vec<N, T, Precision> const *end) {
// transfer ownership to PyObject
size_t nvert = end - begin;
T *buffer = new T[nvert * N];
nb::capsule mem_mgr(buffer, [](void *p) noexcept { delete[](T *) p; });
for (int i = 0; i < nvert; i++) {
for (int j = 0; j < N; j++) {
buffer[i * N + j] = begin[i][j];
}
}
return {buffer, {nvert, N}, mem_mgr};
}

// helper to convert std::vector<glm::vec*> to numpy
template <class T, int N, glm::qualifier Precision>
nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>> to_numpy(
std::vector<glm::vec<N, T, Precision>> const &vec) {
return to_numpy(vec.data(), vec.data() + vec.size());
}

// helper to convert numpy to std::vector<glm::vec*>
template <class T, size_t N, glm::qualifier Precision = glm::defaultp>
void to_glm_range(nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>> const &arr,
glm::vec<int(N), T, Precision> *begin,
glm::vec<int(N), T, Precision> *end) {
if (arr.shape(0) != end - begin) {
throw std::runtime_error(
"received numpy.shape[0]: " + std::to_string(arr.shape(0)) +
" expected: " + std::to_string(int(end - begin)));
}
for (int i = 0; i < arr.shape(0); i++) {
for (int j = 0; j < N; j++) {
begin[i][j] = arr(i, j);
}
}
}
// helper to convert numpy to std::vector<glm::vec*>
template <class T, size_t N, glm::qualifier Precision = glm::defaultp>
void to_glm_vector(nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>> const &arr,
std::vector<glm::vec<int(N), T, Precision>> &out) {
out.resize(arr.shape(0));
to_glm_range(arr, out.data(), out.data() + out.size());
}

using namespace manifold;

typedef std::tuple<float, float> Float2;
typedef std::tuple<float, float, float> Float3;

using NumpyFloatNx2 = nb::ndarray<nb::numpy, float, nb::shape<nb::any, 2>>;
using NumpyFloatNx3 = nb::ndarray<nb::numpy, float, nb::shape<nb::any, 3>>;
using NumpyUintNx3 = nb::ndarray<nb::numpy, uint32_t, nb::shape<nb::any, 3>>;

template <typename T>
std::vector<T> toVector(const T *arr, size_t size) {
return std::vector<T>(arr, arr + size);
Expand Down Expand Up @@ -262,7 +314,23 @@ NB_MODULE(manifold3d, m) {
"one which overlaps, but that is not checked here, so it is up to "
"the user to choose their function with discretion."
"\n\n"
":param warpFunc: A function that modifies a given vertex position.")
":param f: A function that modifies a given vertex position.")
.def(
"warp_batch",
[](Manifold &self,
const std::function<NumpyFloatNx3(NumpyFloatNx3)> &f) {
return self.WarpBatch([&f](glm::vec3 *begin, glm::vec3 *end) {
NumpyFloatNx3 arr = f(to_numpy(begin, end));
to_glm_range(arr, begin, end);
});
},
nb::arg("f"),
"Same as Manifold.warp but calls `f` with a "
"ndarray(shape=(N,3), dtype=float) and expects an ndarray "
"of the same shape and type in return. The input array can be "
"modified and returned if desired. "
"\n\n"
":param f: A function that modifies multiple vertex positions.")
.def(
"set_properties",
[](Manifold &self, int newNumProp,
Expand Down Expand Up @@ -923,7 +991,7 @@ NB_MODULE(manifold3d, m) {
":param m: The affine transform matrix to apply to all the vertices.")
.def(
"warp",
[](CrossSection self, const std::function<Float2(Float2)> &f) {
[](CrossSection &self, const std::function<Float2(Float2)> &f) {
return self.Warp([&f](glm::vec2 &v) {
Float2 fv = f(std::make_tuple(v.x, v.y));
v.x = std::get<0>(fv);
Expand All @@ -937,6 +1005,22 @@ NB_MODULE(manifold3d, m) {
"intersections are not included in the result."
"\n\n"
":param warpFunc: A function that modifies a given vertex position.")
.def(
"warp_batch",
[](CrossSection &self,
const std::function<NumpyFloatNx2(NumpyFloatNx2)> &f) {
return self.WarpBatch([&f](glm::vec2 *begin, glm::vec2 *end) {
NumpyFloatNx2 arr = f(to_numpy(begin, end));
to_glm_range(arr, begin, end);
});
},
nb::arg("f"),
"Same as CrossSection.warp but calls `f` with a "
"ndarray(shape=(N,2), dtype=float) and expects an ndarray "
"of the same shape and type in return. The input array can be "
"modified and returned if desired. "
"\n\n"
":param f: A function that modifies multiple vertex positions.")
.def("simplify", &CrossSection::Simplify, nb::arg("epsilon") = 1e-6,
"Remove vertices from the contours in this CrossSection that are "
"less than the specified distance epsilon from an imaginary line "
Expand Down
2 changes: 2 additions & 0 deletions src/cross_section/include/cross_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class CrossSection {
CrossSection Mirror(const glm::vec2 ax) const;
CrossSection Transform(const glm::mat3x2& m) const;
CrossSection Warp(std::function<void(glm::vec2&)> warpFunc) const;
CrossSection WarpBatch(
std::function<void(glm::vec2*, glm::vec2*)> warpFunc) const;
CrossSection Simplify(double epsilon = 1e-6) const;

// Adapted from Clipper2 docs:
Expand Down
46 changes: 34 additions & 12 deletions src/cross_section/src/cross_section.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,21 +567,43 @@ CrossSection CrossSection::Transform(const glm::mat3x2& m) const {
*/
CrossSection CrossSection::Warp(
std::function<void(glm::vec2&)> warpFunc) const {
auto paths = GetPaths();
auto warped = C2::PathsD();
warped.reserve(paths->paths_.size());
for (auto path : paths->paths_) {
auto sz = path.size();
auto s = C2::PathD(sz);
for (int i = 0; i < sz; ++i) {
auto v = v2_of_pd(path[i]);
warpFunc(v);
s[i] = v2_to_pd(v);
return WarpBatch([&warpFunc](glm::vec2* begin, glm::vec2* end) {
for (glm::vec2* p = begin; p != end; ++p) {
warpFunc(*p);
}
});
}

/**
* Same as CrossSection::Warp but calls warpFunc with begin and end
* iterators to all the vertices to be warped.
* Note that warpFunc begin/end pointers follow c++ iterator
* conventions - the end is exclusive and not to be dereferenced
*
* @param warpFunc A function that modifies multiple vertex positions.
*/
CrossSection CrossSection::WarpBatch(
std::function<void(glm::vec2*, glm::vec2*)> warpFunc) const {
std::vector<glm::vec2> tmp_verts;
C2::PathsD paths = GetPaths()->paths_; // deep copy
for (C2::PathD& path : paths) {
for (C2::PointD& p : path) {
tmp_verts.push_back(v2_of_pd(p));
}
}

warpFunc(tmp_verts.data(), tmp_verts.data() + tmp_verts.size());

auto cursor = tmp_verts.begin();
for (C2::PathD& path : paths) {
for (C2::PointD& p : path) {
p = v2_to_pd(*cursor);
++cursor;
}
warped.push_back(s);
}

return CrossSection(
shared_paths(C2::Union(warped, C2::FillRule::Positive, precision_)));
shared_paths(C2::Union(paths, C2::FillRule::Positive, precision_)));
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/manifold/include/manifold.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class Manifold {
Manifold Transform(const glm::mat4x3&) const;
Manifold Mirror(glm::vec3) const;
Manifold Warp(std::function<void(glm::vec3&)>) const;
Manifold WarpBatch(std::function<void(glm::vec3*, glm::vec3*)>) const;
Manifold SetProperties(
int, std::function<void(float*, glm::vec3, const float*)>) const;
Manifold CalculateCurvature(int gaussianIdx, int meanIdx) const;
Expand Down
9 changes: 8 additions & 1 deletion src/manifold/src/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,14 @@ void Manifold::Impl::MarkFailure(Error status) {
}

void Manifold::Impl::Warp(std::function<void(glm::vec3&)> warpFunc) {
thrust::for_each_n(thrust::host, vertPos_.begin(), NumVert(), warpFunc);
WarpBatch([&warpFunc](glm::vec3* begin, glm::vec3* end) {
thrust::for_each(thrust::host, begin, end, warpFunc);
});
}

void Manifold::Impl::WarpBatch(
std::function<void(glm::vec3*, glm::vec3*)> warpFunc) {
warpFunc(vertPos_.begin(), vertPos_.end());
CalculateBBox();
if (!IsFinite()) {
MarkFailure(Error::NonFiniteVertex);
Expand Down
1 change: 1 addition & 0 deletions src/manifold/src/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ struct Manifold::Impl {
void Update();
void MarkFailure(Error status);
void Warp(std::function<void(glm::vec3&)> warpFunc);
void WarpBatch(std::function<void(glm::vec3*, glm::vec3*)> warpFunc);
Impl Transform(const glm::mat4x3& transform) const;
SparseIndices EdgeCollisions(const Impl& B, bool inverted = false) const;
SparseIndices VertexCollisionsZ(VecView<const glm::vec3> vertsIn,
Expand Down
14 changes: 14 additions & 0 deletions src/manifold/src/manifold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,20 @@ Manifold Manifold::Warp(std::function<void(glm::vec3&)> warpFunc) const {
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}

/**
* Same as Manifold::Warp but calls warpFunc with begin and end
* iterators to all the vertices to be warped.
* Note that warpFunc begin/end pointers follow c++ iterator
* conventions - the end is exclusive and not to be dereferenced
* @param warpFunc A function that modifies multiple vertex positions.
*/
Manifold Manifold::WarpBatch(
std::function<void(glm::vec3*, glm::vec3*)> warpFunc) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
pImpl->WarpBatch(warpFunc);
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}

/**
* Create a new copy of this manifold with updated vertex properties by
* supplying a function that takes the existing position and properties as
Expand Down
Loading

0 comments on commit 77b5cd8

Please sign in to comment.