#pragma once #include #include #include #include #include // CPU sparse marching cubes #include #include // CPU mesh decimator #include #include #include namespace py = pybind11; namespace cubvh { static py::array_t fill_holes( py::array_t vertices, py::array_t faces, bool return_added, bool check_containment, double eps_d, bool verbose) { // Validate shapes auto vbuf = vertices.request(); auto fbuf = faces.request(); if (!(vbuf.ndim == 2 && vbuf.shape[1] == 3)) { throw std::runtime_error("vertices must be of shape [N,3]"); } if (!(fbuf.ndim == 2 && fbuf.shape[1] == 3)) { throw std::runtime_error("faces must be of shape [M,3]"); } const size_t N = static_cast(vbuf.shape[0]); const size_t M = static_cast(fbuf.shape[0]); const float* vptr = static_cast(vbuf.ptr); const int* fptr = static_cast(fbuf.ptr); std::vector V(N); for (size_t i = 0; i < N; ++i) { V[i] = Eigen::Vector3f(vptr[3*i+0], vptr[3*i+1], vptr[3*i+2]); } std::vector F(M); for (size_t i = 0; i < M; ++i) { F[i] = Eigen::Vector3i(fptr[3*i+0], fptr[3*i+1], fptr[3*i+2]); } cubvh::cpu::HoleFillOptions opt; opt.checkContainment = check_containment; opt.eps = static_cast(eps_d); opt.verbose = verbose; if (return_added) { auto added = cubvh::cpu::fill_holes(V, F, opt); py::array_t out({(py::ssize_t)added.size(), (py::ssize_t)3}); auto obuf = out.request(); int* optr = static_cast(obuf.ptr); for (size_t i = 0; i < added.size(); ++i) { optr[3*i+0] = added[i][0]; optr[3*i+1] = added[i][1]; optr[3*i+2] = added[i][2]; } return out; } else { cubvh::cpu::fill_holes_inplace(V, F, opt); py::array_t out({(py::ssize_t)F.size(), (py::ssize_t)3}); auto obuf = out.request(); int* optr = static_cast(obuf.ptr); for (size_t i = 0; i < F.size(); ++i) { optr[3*i+0] = F[i][0]; optr[3*i+1] = F[i][1]; optr[3*i+2] = F[i][2]; } return out; } } // merge_vertices binding: returns (vertices, faces) after merge static std::pair, py::array_t> merge_vertices( py::array_t vertices, py::array_t faces, double threshold_d) { auto vbuf = vertices.request(); auto fbuf = faces.request(); if (!(vbuf.ndim == 2 && vbuf.shape[1] == 3)) { throw std::runtime_error("vertices must be of shape [N,3]"); } if (!(fbuf.ndim == 2 && fbuf.shape[1] == 3)) { throw std::runtime_error("faces must be of shape [M,3]"); } const size_t N = static_cast(vbuf.shape[0]); const size_t M = static_cast(fbuf.shape[0]); const float* vptr = static_cast(vbuf.ptr); const int* fptr = static_cast(fbuf.ptr); std::vector V(N); for (size_t i=0;i F(M); for (size_t i=0;i V_out; std::vector F_out; cubvh::cpu::merge_vertices(V, F, static_cast(threshold_d), V_out, F_out); py::array_t v_out({(py::ssize_t)V_out.size(), (py::ssize_t)3}); py::array_t f_out({(py::ssize_t)F_out.size(), (py::ssize_t)3}); auto vObuf = v_out.request(); auto fObuf = f_out.request(); float* vO = static_cast(vObuf.ptr); int* fO = static_cast(fObuf.ptr); for (size_t i=0;i, py::array_t> sparse_marching_cubes_cpu( py::array_t coords, py::array_t corners, double iso_d, bool ensure_consistency = false) { auto cbuf = coords.request(); auto vbuf = corners.request(); if (!(cbuf.ndim == 2 && cbuf.shape[1] == 3)) { throw std::runtime_error("coords must be of shape [N,3] (int32)"); } if (!(vbuf.ndim == 2 && vbuf.shape[1] == 8)) { throw std::runtime_error("corners must be of shape [N,8] (float32)"); } if (cbuf.shape[0] != vbuf.shape[0]) { throw std::runtime_error("coords and corners must have the same first dimension N"); } const int N = static_cast(cbuf.shape[0]); const int* cptr = static_cast(cbuf.ptr); const float* fptr = static_cast(vbuf.ptr); const float iso = static_cast(iso_d); auto mesh = cubvh::cpu::sparse_marching_cubes(cptr, fptr, N, iso, ensure_consistency); const auto& V = mesh.first; const auto& F = mesh.second; // Allocate outputs py::array_t v_out({(py::ssize_t)V.size(), (py::ssize_t)3}); py::array_t f_out({(py::ssize_t)F.size(), (py::ssize_t)3}); auto vObuf = v_out.request(); auto fObuf = f_out.request(); float* vO = static_cast(vObuf.ptr); int* fO = static_cast(fObuf.ptr); for (size_t i = 0; i < V.size(); ++i) { vO[3*i+0] = V[i].x; vO[3*i+1] = V[i].y; vO[3*i+2] = V[i].z; } for (size_t i = 0; i < F.size(); ++i) { fO[3*i+0] = F[i].v0; fO[3*i+1] = F[i].v1; fO[3*i+2] = F[i].v2; } return {v_out, f_out}; } // CPU decimator bindings template static cubvh::cpu::qd::MeshT _mesh_from_numpy_typed( py::array_t vertices, py::array_t faces) { if (vertices.ndim() != 2 || vertices.shape(1) != 3) throw std::runtime_error("vertices must be (N,3) array"); if (faces.ndim() != 2 || faces.shape(1) != 3) throw std::runtime_error("faces must be (M,3) int32 array"); cubvh::cpu::qd::MeshT m; m.vertices.reserve(vertices.shape(0)); auto vbuf = vertices.template unchecked<2>(); for (ssize_t i = 0; i < vertices.shape(0); ++i) { m.vertices.emplace_back(vbuf(i,0), vbuf(i,1), vbuf(i,2)); } m.faces.reserve(faces.shape(0)); auto fbuf = faces.template unchecked<2>(); for (ssize_t i = 0; i < faces.shape(0); ++i) { m.faces.push_back({ fbuf(i,0), fbuf(i,1), fbuf(i,2) }); } return m; } template static std::pair, py::array_t> _mesh_to_numpy_typed(const cubvh::cpu::qd::MeshT& m) { py::array_t V({ (ssize_t)m.vertices.size(), (ssize_t)3 }); py::array_t F({ (ssize_t)m.faces.size(), (ssize_t)3 }); auto vbuf = V.template mutable_unchecked<2>(); for (ssize_t i = 0; i < (ssize_t)m.vertices.size(); ++i) { vbuf(i,0) = m.vertices[i].x; vbuf(i,1) = m.vertices[i].y; vbuf(i,2) = m.vertices[i].z; } auto fbuf = F.template mutable_unchecked<2>(); for (ssize_t i = 0; i < (ssize_t)m.faces.size(); ++i) { fbuf(i,0) = m.faces[i][0]; fbuf(i,1) = m.faces[i][1]; fbuf(i,2) = m.faces[i][2]; } return { V, F }; } static py::tuple decimate(py::array vertices, py::array faces, int target_vertices) { py::dtype dt = vertices.dtype(); if (dt.is(py::dtype::of())){ auto v = vertices.cast>(); auto f = faces.cast>(); auto mesh = _mesh_from_numpy_typed(v, f); cubvh::cpu::qd::DecimatorT dec(mesh); dec.decimate(target_vertices); auto out = _mesh_to_numpy_typed(dec.mesh()); return py::make_tuple(out.first, out.second); } else if (dt.is(py::dtype::of())){ auto v = vertices.cast>(); auto f = faces.cast>(); auto mesh = _mesh_from_numpy_typed(v, f); cubvh::cpu::qd::DecimatorT dec(mesh); dec.decimate(target_vertices); auto out = _mesh_to_numpy_typed(dec.mesh()); return py::make_tuple(out.first, out.second); } else { throw std::runtime_error("vertices must be float32 or float64 array"); } } static py::tuple parallel_decimate(py::array vertices, py::array faces, int target_vertices) { py::dtype dt = vertices.dtype(); if (dt.is(py::dtype::of())){ auto v = vertices.cast>(); auto f = faces.cast>(); auto mesh = _mesh_from_numpy_typed(v, f); cubvh::cpu::qd::DecimatorT dec(mesh); dec.parallelDecimate(target_vertices); auto out = _mesh_to_numpy_typed(dec.mesh()); return py::make_tuple(out.first, out.second); } else if (dt.is(py::dtype::of())){ auto v = vertices.cast>(); auto f = faces.cast>(); auto mesh = _mesh_from_numpy_typed(v, f); cubvh::cpu::qd::DecimatorT dec(mesh); dec.parallelDecimate(target_vertices); auto out = _mesh_to_numpy_typed(dec.mesh()); return py::make_tuple(out.first, out.second); } else { throw std::runtime_error("vertices must be float32 or float64 array"); } } // CPU Hash Table bindings class HashTable { public: HashTable() {} void set_num_dims(int d) { ht.set_num_dims(d); } int get_num_dims() const { return ht.get_num_dims(); } void resize(int capacity) { ht.resize(capacity); } void prepare() { ht.prepare(); } void insert(at::Tensor coords) { TORCH_CHECK(!coords.is_cuda(), "coords must reside on CPU"); TORCH_CHECK(coords.dtype() == at::kInt, "coords must be int32"); TORCH_CHECK(coords.dim() == 2, "coords must be 2D [N,D]"); coords_ref_ = coords.contiguous(); const int N = (int)coords_ref_.size(0); const int D = (int)coords_ref_.size(1); ht.set_num_dims(D); ht.insert(coords_ref_.data_ptr(), N); } void build(at::Tensor coords) { TORCH_CHECK(!coords.is_cuda(), "coords must reside on CPU"); TORCH_CHECK(coords.dtype() == at::kInt, "coords must be int32"); TORCH_CHECK(coords.dim() == 2, "coords must be 2D [N,D]"); coords_ref_ = coords.contiguous(); const int N = (int)coords_ref_.size(0); const int D = (int)coords_ref_.size(1); ht.set_num_dims(D); ht.build(coords_ref_.data_ptr(), N); } at::Tensor search(at::Tensor queries) const { TORCH_CHECK(!queries.is_cuda(), "queries must reside on CPU"); TORCH_CHECK(queries.dtype() == at::kInt, "queries must be int32"); TORCH_CHECK(queries.dim() == 2, "queries must be 2D [M,D]"); TORCH_CHECK(ht.capacity > 0, "hash table is not built"); at::Tensor q = queries.contiguous(); const int M = (int)q.size(0); auto opts_i = torch::TensorOptions().dtype(torch::kInt32).device(q.device()); at::Tensor out = at::empty({M}, opts_i); ht.search(q.data_ptr(), M, out.data_ptr()); return out; } private: mutable HashTableIntCPU ht; at::Tensor coords_ref_; }; } // namespace cubvh