#include #include #include #include #include #include "api.h" constexpr size_t kInvalidIndex = std::numeric_limits::max(); struct float3 {float x, y, z; float& operator[](int i) {return (&x)[i];}}; struct int3 {int x, y, z; int& operator[](int i) {return (&x)[i];}}; struct int4 {int x, y, z, w; int& operator[](int i) {return (&x)[i];}}; struct bool3 {bool x, y, z; bool& operator[](int i) {return (&x)[i];}}; template static inline U lerp(const T& a, const T& b, const T& t, const U& val_a, const U& val_b) { if (a == b) return val_a; // Avoid divide by zero T alpha = (t - a) / (b - a); return (1 - alpha) * val_a + alpha * val_b; } template static auto get_or_default(const Map& map, const Key& key, const Default& default_val) -> typename Map::mapped_type { auto it = map.find(key); return (it != map.end()) ? it->second : default_val; } // 3D voxel coordinate struct VoxelCoord { int x, y, z; int& operator[](int i) { return (&x)[i]; } bool operator==(const VoxelCoord& other) const { return x == other.x && y == other.y && z == other.z; } }; // Hash function for VoxelCoord to use in unordered_map namespace std { template <> struct hash { size_t operator()(const VoxelCoord& v) const { const std::size_t p1 = 73856093; const std::size_t p2 = 19349663; const std::size_t p3 = 83492791; return (std::size_t)(v.x) * p1 ^ (std::size_t)(v.y) * p2 ^ (std::size_t)(v.z) * p3; } }; } void intersect_qef( const Eigen::Vector3f& voxel_size, const Eigen::Vector3i& grid_min, const Eigen::Vector3i& grid_max, const std::vector& triangles, // 3 vertices per triangle std::unordered_map& hash_table, // Hash table for voxel lookup std::vector& voxels, // Output: Voxel coordinates std::vector& means, // Output: Mean vertex positions for each voxel std::vector& cnt, // Output: Number of intersections for each voxel std::vector& intersected, // Output: Whether edge of voxel intersects with triangle std::vector& qefs // Output: QEF matrices for each voxel ) { const size_t N_tri = triangles.size() / 3; for (size_t i = 0; i < N_tri; ++i) { const Eigen::Vector3f& v0 = triangles[i * 3 + 0]; const Eigen::Vector3f& v1 = triangles[i * 3 + 1]; const Eigen::Vector3f& v2 = triangles[i * 3 + 2]; // Compute edge vectors and face normal Eigen::Vector3f e0 = v1 - v0; Eigen::Vector3f e1 = v2 - v1; Eigen::Vector3f n = e0.cross(e1).normalized(); Eigen::Vector4f plane; plane << n.x(), n.y(), n.z(), -n.dot(v0); auto Q = plane * plane.transpose(); // Scan-line algorithm to find intersections with the voxel grid from three directions /* t0 | \ | t1 | / t2 */ auto scan_line_fill = [&] (const int ax2) { int ax0 = (ax2 + 1) % 3; int ax1 = (ax2 + 2) % 3; // Canonical question std::array t = { Eigen::Vector3d(v0[ax0], v0[ax1], v0[ax2]), Eigen::Vector3d(v1[ax0], v1[ax1], v1[ax2]), Eigen::Vector3d(v2[ax0], v2[ax1], v2[ax2]) }; std::sort(t.begin(), t.end(), [](const Eigen::Vector3d& a, const Eigen::Vector3d& b) { return a.y() < b.y(); }); // Scan-line algorithm int start = std::clamp(int(t[0].y() / voxel_size[ax1]), grid_min[ax1], grid_max[ax1] - 1); int mid = std::clamp(int(t[1].y() / voxel_size[ax1]), grid_min[ax1], grid_max[ax1] - 1); int end = std::clamp(int(t[2].y() / voxel_size[ax1]), grid_min[ax1], grid_max[ax1] - 1); auto scan_line_half = [&] (const int row_start, const int row_end, const Eigen::Vector3d t0, const Eigen::Vector3d t1, const Eigen::Vector3d t2) { /* t0 | \ t3-t4 | \ t1---t2 */ for (int y_idx = row_start; y_idx < row_end; ++y_idx) { double y = (y_idx + 1) * voxel_size[ax1]; Eigen::Vector2d t3 = lerp(t0.y(), t1.y(), y, Eigen::Vector2d(t0.x(), t0.z()), Eigen::Vector2d(t1.x(), t1.z())); Eigen::Vector2d t4 = lerp(t0.y(), t2.y(), y, Eigen::Vector2d(t0.x(), t0.z()), Eigen::Vector2d(t2.x(), t2.z())); if (t3.x() > t4.x()) std::swap(t3, t4); int line_start = std::clamp(int(t3.x() / voxel_size[ax0]), grid_min[ax0], grid_max[ax0] - 1); int line_end = std::clamp(int(t4.x() / voxel_size[ax0]), grid_min[ax0], grid_max[ax0] - 1); for (int x_idx = line_start; x_idx < line_end; ++x_idx) { double x = (x_idx + 1) * voxel_size[ax0]; double z = lerp(t3.x(), t4.x(), x, t3.y(), t4.y()); int z_idx = int(z / voxel_size[ax2]); if (z_idx >= grid_min[ax2] && z_idx < grid_max[ax2]) { // For 4-connected voxels for (int dx = 0; dx < 2; ++dx) { for (int dy = 0; dy < 2; ++dy) { VoxelCoord coord; coord[ax0] = x_idx + dx; coord[ax1] = y_idx + dy; coord[ax2] = z_idx; Eigen::Vector3d intersect; intersect[ax0] = x; intersect[ax1] = y; intersect[ax2] = z; auto kv = hash_table.find(coord); if (kv == hash_table.end()) { hash_table[coord] = voxels.size(); voxels.push_back({coord.x, coord.y, coord.z}); means.push_back(intersect.cast()); cnt.push_back(1); intersected.push_back({false, false, false}); qefs.push_back(Q); if (dx == 0 && dy == 0) intersected.back()[ax2] = true; } else { auto i = kv->second; means[i] += intersect.cast(); cnt[i] += 1; if (dx == 0 && dy == 0) intersected[i][ax2] = true; qefs[i] += Q; } } } } } } }; scan_line_half(start, mid, t[0], t[1], t[2]); scan_line_half(mid, end, t[2], t[1], t[0]); }; scan_line_fill(0); scan_line_fill(1); scan_line_fill(2); } } void face_qef( const Eigen::Vector3f& voxel_size, const Eigen::Vector3i& grid_min, const Eigen::Vector3i& grid_max, const std::vector& triangles, // 3 vertices per triangle std::unordered_map& hash_table, // Hash table for voxel lookup std::vector& qefs // Output: QEF matrices for each voxel ) { const size_t N_tri = triangles.size() / 3; for (size_t i = 0; i < N_tri; ++i) { const Eigen::Vector3f& v0 = triangles[i * 3 + 0]; const Eigen::Vector3f& v1 = triangles[i * 3 + 1]; const Eigen::Vector3f& v2 = triangles[i * 3 + 2]; // Compute edge vectors and face normal Eigen::Vector3f e0 = v1 - v0; Eigen::Vector3f e1 = v2 - v1; Eigen::Vector3f e2 = v0 - v2; Eigen::Vector3f n = e0.cross(e1).normalized(); Eigen::Vector4f plane; plane << n.x(), n.y(), n.z(), -n.dot(v0); auto Q = plane * plane.transpose(); // Compute triangle bounding box in voxel coordinates Eigen::Vector3f bb_min_f = v0.cwiseMin(v1).cwiseMin(v2).cwiseQuotient(voxel_size); Eigen::Vector3f bb_max_f = v0.cwiseMax(v1).cwiseMax(v2).cwiseQuotient(voxel_size); Eigen::Vector3i bb_min(std::max(static_cast(bb_min_f.x()), grid_min.x()), std::max(static_cast(bb_min_f.y()), grid_min.y()), std::max(static_cast(bb_min_f.z()), grid_min.z())); Eigen::Vector3i bb_max(std::min(static_cast(bb_max_f.x() + 1), grid_max.x()), std::min(static_cast(bb_max_f.y() + 1), grid_max.y()), std::min(static_cast(bb_max_f.z() + 1), grid_max.z())); // Plane test setup Eigen::Vector3f c( n.x() > 0.0f ? voxel_size.x() : 0.0f, n.y() > 0.0f ? voxel_size.y() : 0.0f, n.z() > 0.0f ? voxel_size.z() : 0.0f ); float d1 = n.dot(c - v0); float d2 = n.dot(voxel_size - c - v0); // XY plane projection test setup int mul_xy = (n.z() < 0.0f) ? -1 : 1; Eigen::Vector2f n_xy_e0(-mul_xy * e0.y(), mul_xy * e0.x()); Eigen::Vector2f n_xy_e1(-mul_xy * e1.y(), mul_xy * e1.x()); Eigen::Vector2f n_xy_e2(-mul_xy * e2.y(), mul_xy * e2.x()); float d_xy_e0 = -n_xy_e0.dot(v0.head<2>()) + n_xy_e0.cwiseMax(0.0f).dot(voxel_size.head<2>()); float d_xy_e1 = -n_xy_e1.dot(v1.head<2>()) + n_xy_e1.cwiseMax(0.0f).dot(voxel_size.head<2>()); float d_xy_e2 = -n_xy_e2.dot(v2.head<2>()) + n_xy_e2.cwiseMax(0.0f).dot(voxel_size.head<2>()); // YZ plane projection test setup int mul_yz = (n.x() < 0.0f) ? -1 : 1; Eigen::Vector2f n_yz_e0(-mul_yz * e0.z(), mul_yz * e0.y()); Eigen::Vector2f n_yz_e1(-mul_yz * e1.z(), mul_yz * e1.y()); Eigen::Vector2f n_yz_e2(-mul_yz * e2.z(), mul_yz * e2.y()); float d_yz_e0 = -n_yz_e0.dot(Eigen::Vector2f(v0.y(), v0.z())) + n_yz_e0.cwiseMax(0.0f).dot(Eigen::Vector2f(voxel_size.y(), voxel_size.z())); float d_yz_e1 = -n_yz_e1.dot(Eigen::Vector2f(v1.y(), v1.z())) + n_yz_e1.cwiseMax(0.0f).dot(Eigen::Vector2f(voxel_size.y(), voxel_size.z())); float d_yz_e2 = -n_yz_e2.dot(Eigen::Vector2f(v2.y(), v2.z())) + n_yz_e2.cwiseMax(0.0f).dot(Eigen::Vector2f(voxel_size.y(), voxel_size.z())); // ZX plane projection test setup int mul_zx = (n.y() < 0.0f) ? -1 : 1; Eigen::Vector2f n_zx_e0(-mul_zx * e0.x(), mul_zx * e0.z()); Eigen::Vector2f n_zx_e1(-mul_zx * e1.x(), mul_zx * e1.z()); Eigen::Vector2f n_zx_e2(-mul_zx * e2.x(), mul_zx * e2.z()); float d_zx_e0 = -n_zx_e0.dot(Eigen::Vector2f(v0.z(), v0.x())) + n_zx_e0.cwiseMax(0.0f).dot(Eigen::Vector2f(voxel_size.z(), voxel_size.x())); float d_zx_e1 = -n_zx_e1.dot(Eigen::Vector2f(v1.z(), v1.x())) + n_zx_e1.cwiseMax(0.0f).dot(Eigen::Vector2f(voxel_size.z(), voxel_size.x())); float d_zx_e2 = -n_zx_e2.dot(Eigen::Vector2f(v2.z(), v2.x())) + n_zx_e2.cwiseMax(0.0f).dot(Eigen::Vector2f(voxel_size.z(), voxel_size.x())); // Loop over candidate voxels inside bounding box for (int z = bb_min.z(); z < bb_max.z(); ++z) { for (int y = bb_min.y(); y < bb_max.y(); ++y) { for (int x = bb_min.x(); x < bb_max.x(); ++x) { // Voxel center Eigen::Vector3f p = voxel_size.cwiseProduct(Eigen::Vector3f(x, y, z)); // Plane through box test float nDOTp = n.dot(p); if (((nDOTp + d1) * (nDOTp + d2)) > 0.0f) continue; // XY projection test Eigen::Vector2f p_xy(p.x(), p.y()); if (n_xy_e0.dot(p_xy) + d_xy_e0 < 0) continue; if (n_xy_e1.dot(p_xy) + d_xy_e1 < 0) continue; if (n_xy_e2.dot(p_xy) + d_xy_e2 < 0) continue; // YZ projection test Eigen::Vector2f p_yz(p.y(), p.z()); if (n_yz_e0.dot(p_yz) + d_yz_e0 < 0) continue; if (n_yz_e1.dot(p_yz) + d_yz_e1 < 0) continue; if (n_yz_e2.dot(p_yz) + d_yz_e2 < 0) continue; // ZX projection test Eigen::Vector2f p_zx(p.z(), p.x()); if (n_zx_e0.dot(p_zx) + d_zx_e0 < 0) continue; if (n_zx_e1.dot(p_zx) + d_zx_e1 < 0) continue; if (n_zx_e2.dot(p_zx) + d_zx_e2 < 0) continue; // Passed all tests — mark voxel auto coord = VoxelCoord{x, y, z}; auto kv = hash_table.find(coord); if (kv != hash_table.end()) { qefs[kv->second] += Q; } } } } } } void boundry_qef( const Eigen::Vector3f& voxel_size, const Eigen::Vector3i& grid_min, const Eigen::Vector3i& grid_max, const std::vector& boundries, // 2 vertices per segment const float boundary_weight, // Weight for boundary edges std::unordered_map& hash_table, // Hash table for voxel lookup std::vector& qefs // Output: QEF matrices for each voxel ) { for (size_t i = 0; i < boundries.size() / 2; ++i) { const Eigen::Vector3f& v0 = boundries[i * 2 + 0]; const Eigen::Vector3f& v1 = boundries[i * 2 + 1]; // Calculate the QEF for the edge (boundary) defined by v0 and v1 Eigen::Vector3d dir(v1.x() - v0.x(), v1.y() - v0.y(), v1.z() - v0.z()); double segment_length = dir.norm(); if (segment_length < 1e-6) continue; // Skip degenerate edges (zero-length) dir.normalize(); // unit direction vector // Projection matrix orthogonal to the direction: I - d d^T Eigen::Matrix3f A = Eigen::Matrix3f::Identity() - (dir * dir.transpose()).cast(); // b = -A * v0 Eigen::Vector3f b = -A * v0; // c = v0^T * A * v0 float c = v0.transpose() * A * v0; // Now pack this into a 4x4 QEF matrix Eigen::Matrix4f Q = Eigen::Matrix4f::Zero(); Q.block<3, 3>(0, 0) = A; Q.block<3, 1>(0, 3) = b; Q.block<1, 3>(3, 0) = b.transpose(); Q(3, 3) = c; // DDA Traversal logic directly inside the function // Starting and ending voxel coordinates Eigen::Vector3i v0_voxel = (v0.cwiseQuotient(voxel_size)).array().floor().cast(); Eigen::Vector3i v1_voxel = (v1.cwiseQuotient(voxel_size)).array().floor().cast(); // Determine step direction for each axis based on the line direction Eigen::Vector3i step = (dir.array() > 0).select(Eigen::Vector3i(1, 1, 1), Eigen::Vector3i(-1, -1, -1)); Eigen::Vector3d tMax, tDelta; for (int axis = 0; axis < 3; ++axis) { if (dir[axis] == 0.0) { tMax[axis] = std::numeric_limits::infinity(); tDelta[axis] = std::numeric_limits::infinity(); } else { float voxel_border = voxel_size[axis] * (v0_voxel[axis] + (step[axis] > 0 ? 1 : 0)); tMax[axis] = (voxel_border - v0[axis]) / dir[axis]; tDelta[axis] = voxel_size[axis] / std::abs(dir[axis]); } } // Current voxel position Eigen::Vector3i current = v0_voxel; // Store the voxel we start at std::vector voxels; voxels.push_back({current.x(), current.y(), current.z()}); // Traverse the voxels while (true) { int axis; if (tMax.x() < tMax.y()) { axis = (tMax.x() < tMax.z()) ? 0 : 2; } else { axis = (tMax.y() < tMax.z()) ? 1 : 2; } if (tMax[axis] > segment_length) break; current[axis] += step[axis]; tMax[axis] += tDelta[axis]; voxels.push_back({current.x(), current.y(), current.z()}); } // Accumulate QEF for each voxel passed through for (const auto& coord : voxels) { // Make sure the voxel is within bounds if ((coord.x < grid_min.x() || coord.x >= grid_max.x()) || (coord.y < grid_min.y() || coord.y >= grid_max.y()) || (coord.z < grid_min.z() || coord.z >= grid_max.z())) continue; if (!hash_table.count(coord)) continue; // Skip if voxel not in hash table // Accumulate the QEF for this voxel qefs[hash_table[coord]] += boundary_weight * Q; // Scale by boundary weight } } } std::array quad_to_2tri( const std::vector& vertices, const int4& quad_indices ) { int ia = quad_indices.x; int ib = quad_indices.y; int ic = quad_indices.z; int id = quad_indices.w; Eigen::Vector3f a(vertices[ia].x, vertices[ia].y, vertices[ia].z); Eigen::Vector3f b(vertices[ib].x, vertices[ib].y, vertices[ib].z); Eigen::Vector3f c(vertices[ic].x, vertices[ic].y, vertices[ic].z); Eigen::Vector3f d(vertices[id].x, vertices[id].y, vertices[id].z); // diagonal AC Eigen::Vector3f n_abc = (b - a).cross(c - a).normalized(); Eigen::Vector3f n_acd = (c - a).cross(d - a).normalized(); float angle_ac = std::acos(std::clamp(n_abc.dot(n_acd), -1.0f, 1.0f)); // diagonal BD Eigen::Vector3f n_abd = (b - a).cross(d - a).normalized(); Eigen::Vector3f n_bcd = (c - b).cross(d - b).normalized(); float angle_bd = std::acos(std::clamp(n_abd.dot(n_bcd), -1.0f, 1.0f)); if (angle_ac <= angle_bd) { return {int3{ia, ib, ic}, int3{ia, ic, id}}; } else { return {int3{ia, ib, id}, int3{ib, ic, id}}; } } void face_from_dual_vertices( const std::unordered_map& hash_table, const std::vector& voxels, const std::vector& dual_vertices, const std::vector& intersected, std::vector& face_indices ) { for (int i = 0; i < dual_vertices.size(); ++i) { int3 coord = voxels[i]; bool3 is_intersected = intersected[i]; // Check existence of neighboring 6 voxels size_t neigh_indices[6] = { get_or_default(hash_table, VoxelCoord{coord.x + 1, coord.y, coord.z}, kInvalidIndex), get_or_default(hash_table, VoxelCoord{coord.x, coord.y + 1, coord.z}, kInvalidIndex), get_or_default(hash_table, VoxelCoord{coord.x + 1, coord.y + 1, coord.z}, kInvalidIndex), get_or_default(hash_table, VoxelCoord{coord.x, coord.y, coord.z + 1}, kInvalidIndex), get_or_default(hash_table, VoxelCoord{coord.x + 1, coord.y, coord.z + 1}, kInvalidIndex), get_or_default(hash_table, VoxelCoord{coord.x, coord.y + 1, coord.z + 1}, kInvalidIndex) }; // xy-plane if (is_intersected[2] && neigh_indices[0] != kInvalidIndex && neigh_indices[1] != kInvalidIndex && neigh_indices[2] != kInvalidIndex) { int4 quad_indices{i, neigh_indices[0], neigh_indices[2], neigh_indices[1]}; auto tri_indices = quad_to_2tri(dual_vertices, quad_indices); face_indices.insert(face_indices.end(), tri_indices.begin(), tri_indices.end()); } // yz-plane if (is_intersected[0] && neigh_indices[1] != kInvalidIndex && neigh_indices[3] != kInvalidIndex && neigh_indices[5] != kInvalidIndex) { int4 quad_indices{i, neigh_indices[1], neigh_indices[5], neigh_indices[3]}; auto tri_indices = quad_to_2tri(dual_vertices, quad_indices); face_indices.insert(face_indices.end(), tri_indices.begin(), tri_indices.end()); } // xz-plane if (is_intersected[1] && neigh_indices[0] != kInvalidIndex && neigh_indices[3] != kInvalidIndex && neigh_indices[4] != kInvalidIndex) { int4 quad_indices{i, neigh_indices[0], neigh_indices[4], neigh_indices[3]}; auto tri_indices = quad_to_2tri(dual_vertices, quad_indices); face_indices.insert(face_indices.end(), tri_indices.begin(), tri_indices.end()); } } } /** * Extract flexible dual grid from a triangle mesh. * * @param vertices: Tensor of shape (N, 3) containing vertex positions. * @param faces: Tensor of shape (M, 3) containing triangle vertex indices. * @param voxel_size: Tensor of shape (3,) containing the voxel size in each dimension. * @param grid_range: Tensor of shape (2, 3) containing the minimum and maximum coordinates of the grid range. * @param face_weight: Weight for the face edges in the QEF computation. * @param boundary_weight: Weight for the boundary edges in the QEF computation. * @param regularization_weight: Regularization factor to apply to the QEF matrices. * @param timing: Boolean flag to indicate whether to print timing information. * * @return a tuple ((x, y, z), vertices, intersected, faces) containing the remeshed vertices and the corresponding voxel grid. */ std::tuple mesh_to_flexible_dual_grid_cpu( const torch::Tensor& vertices, const torch::Tensor& faces, const torch::Tensor& voxel_size, const torch::Tensor& grid_range, float face_weight, float boundary_weight, float regularization_weight, bool timing ) { const int F = faces.size(0); const float* v_ptr = vertices.data_ptr(); const int* f_ptr = faces.data_ptr(); const float* voxel_size_ptr = voxel_size.data_ptr(); const int* grid_range_ptr = grid_range.data_ptr(); clock_t start, end; std::unordered_map hash_table; std::vector voxels; // Voxel coordinates std::vector means; // Mean vertex positions for each voxel std::vector cnt; // Number of intersections for each voxel std::vector intersected; // Indicate whether edges of voxels intersect with surface std::vector qefs; // QEF matrices for each voxel // Convert tensors to Eigen types Eigen::Vector3f e_voxel_size(voxel_size_ptr[0], voxel_size_ptr[1], voxel_size_ptr[2]); Eigen::Vector3i e_grid_min(grid_range_ptr[0], grid_range_ptr[1], grid_range_ptr[2]); Eigen::Vector3i e_grid_max(grid_range_ptr[3], grid_range_ptr[4], grid_range_ptr[5]); // Intersect QEF computation start = clock(); std::vector triangles; triangles.reserve(F * 3); for (int f = 0; f < F; ++f) { for (int v = 0; v < 3; ++v) { triangles.push_back(Eigen::Vector3f( v_ptr[f_ptr[f * 3 + v] * 3 + 0], v_ptr[f_ptr[f * 3 + v] * 3 + 1], v_ptr[f_ptr[f * 3 + v] * 3 + 2] )); } } intersect_qef(e_voxel_size, e_grid_min, e_grid_max, triangles, hash_table, voxels, means, cnt, intersected, qefs); end = clock(); if (timing) std::cout << "Intersect QEF computation took " << double(end - start) / CLOCKS_PER_SEC << " seconds." << std::endl; // Face QEF computation if (face_weight > 0.0f) { start = clock(); face_qef(e_voxel_size, e_grid_min, e_grid_max, triangles, hash_table, qefs); end = clock(); if (timing) std::cout << "Face QEF computation took " << double(end - start) / CLOCKS_PER_SEC << " seconds." << std::endl; } // Boundary QEF computation if (boundary_weight > 0.0f) { start = clock(); std::map, int> edge_count; for (int f = 0; f < F; ++f) { for (int v0 = 0; v0 < 3; ++v0) { int e0 = f_ptr[f * 3 + v0]; int e1 = f_ptr[f * 3 + (v0 + 1) % 3]; if (e0 > e1) std::swap(e0, e1); edge_count[std::make_pair(e0, e1)]++; } } std::vector boundries; for (const auto& e : edge_count) { if (e.second == 1) { int v0 = e.first.first; int v1 = e.first.second; boundries.push_back(Eigen::Vector3f( v_ptr[v0 * 3 + 0], v_ptr[v0 * 3 + 1], v_ptr[v0 * 3 + 2] )); boundries.push_back(Eigen::Vector3f( v_ptr[v1 * 3 + 0], v_ptr[v1 * 3 + 1], v_ptr[v1 * 3 + 2] )); } } boundry_qef(e_voxel_size, e_grid_min, e_grid_max, boundries, boundary_weight, hash_table, qefs); end = clock(); if (timing) std::cout << "Boundary QEF computation took " << double(end - start) / CLOCKS_PER_SEC << " seconds." << std::endl; } // Solve the QEF system to obtain final dual vertices start = clock(); std::vector dual_vertices(voxels.size()); for (int i = 0; i < voxels.size(); ++i) { int3 coord = voxels[i]; Eigen::Matrix4f Q = qefs[i]; float min_corner[3] = { coord.x * e_voxel_size.x(), coord.y * e_voxel_size.y(), coord.z * e_voxel_size.z() }; float max_corner[3] = { (coord.x + 1) * e_voxel_size.x(), (coord.y + 1) * e_voxel_size.y(), (coord.z + 1) * e_voxel_size.z() }; // Add regularization term if (regularization_weight > 0.0f) { Eigen::Vector3f p = means[i] / cnt[i]; // Construct the QEF matrix for this vertex Eigen::Matrix4f Qreg = Eigen::Matrix4f::Zero(); Qreg.topLeftCorner<3,3>() = Eigen::Matrix3f::Identity(); Qreg.block<3,1>(0,3) = -p; Qreg.block<1,3>(3,0) = -p.transpose(); Qreg(3,3) = p.dot(p); Q += regularization_weight * cnt[i] * Qreg; // Scale by regularization weight } // Solve unconstrained Eigen::Matrix3f A = Q.topLeftCorner<3, 3>(); Eigen::Vector3f b = -Q.block<3, 1>(0, 3); Eigen::Vector3f v_new = A.colPivHouseholderQr().solve(b); if (!( v_new.x() >= min_corner[0] && v_new.x() <= max_corner[0] && v_new.y() >= min_corner[1] && v_new.y() <= max_corner[1] && v_new.z() >= min_corner[2] && v_new.z() <= max_corner[2] )) { // Starting enumeration of constraints float best = std::numeric_limits::infinity(); // Solve single-constraint auto solve_single_constraint = [&](int fixed_axis) { int ax1 = (fixed_axis + 1) % 3; int ax2 = (fixed_axis + 2) % 3; Eigen::Matrix2f A; Eigen::Matrix2f B; Eigen::Vector2f q, b, x; A << Q(ax1, ax1), Q(ax1, ax2), Q(ax2, ax1), Q(ax2, ax2); B << Q(ax1, fixed_axis), Q(ax1, 3), Q(ax2, fixed_axis), Q(ax2, 3); auto Asol = A.colPivHouseholderQr(); // if lower bound q << min_corner[fixed_axis], 1; b = -B * q; x = Asol.solve(b); if ( x.x() >= min_corner[ax1] && x.x() <= max_corner[ax1] && x.y() >= min_corner[ax2] && x.y() <= max_corner[ax2] ) { Eigen::Vector4f p; p[fixed_axis] = min_corner[fixed_axis]; p[ax1] = x.x(); p[ax2] = x.y(); p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } // if upper bound q << max_corner[fixed_axis], 1; b = -B * q; x = Asol.solve(b); if ( x.x() >= min_corner[ax1] && x.x() <= max_corner[ax1] && x.y() >= min_corner[ax2] && x.y() <= max_corner[ax2] ) { Eigen::Vector4f p; p[fixed_axis] = max_corner[fixed_axis]; p[ax1] = x.x(); p[ax2] = x.y(); p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } }; solve_single_constraint(0); // fix x solve_single_constraint(1); // fix y solve_single_constraint(2); // fix z // Solve two-constraint auto solve_two_constraint = [&](int free_axis) { int ax1 = (free_axis + 1) % 3; int ax2 = (free_axis + 2) % 3; float a, x; Eigen::Vector3f b, q; a = Q(free_axis, free_axis); b << Q(free_axis, ax1), Q(free_axis, ax2), Q(free_axis, 3); // if lower-lower bound q << min_corner[ax1], min_corner[ax2], 1; x = -(b.dot(q)) / a; if (x >= min_corner[free_axis] && x <= max_corner[free_axis]) { Eigen::Vector4f p; p[free_axis] = x; p[ax1] = min_corner[ax1]; p[ax2] = min_corner[ax2]; p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } // if lower-upper bound q << min_corner[ax1], max_corner[ax2], 1; x = -(b.dot(q)) / a; if (x >= min_corner[free_axis] && x <= max_corner[free_axis]) { Eigen::Vector4f p; p[free_axis] = x; p[ax1] = min_corner[ax1]; p[ax2] = max_corner[ax2]; p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } // if upper-lower bound q << max_corner[ax1], min_corner[ax2], 1; x = -(b.dot(q)) / a; if (x >= min_corner[free_axis] && x <= max_corner[free_axis]) { Eigen::Vector4f p; p[free_axis] = x; p[ax1] = max_corner[ax1]; p[ax2] = min_corner[ax2]; p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } // if upper-upper bound q << max_corner[ax1], max_corner[ax2], 1; x = -(b.dot(q)) / a; if (x >= min_corner[free_axis] && x <= max_corner[free_axis]) { Eigen::Vector4f p; p[free_axis] = x; p[ax1] = max_corner[ax1]; p[ax2] = max_corner[ax2]; p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } }; solve_two_constraint(0); // free x solve_two_constraint(1); // free y solve_two_constraint(2); // free z // Solve three-constraint for (int x_constraint = 0; x_constraint < 2; ++x_constraint) { for (int y_constraint = 0; y_constraint < 2; ++y_constraint) { for (int z_constraint = 0; z_constraint < 2; ++z_constraint) { Eigen::Vector4f p; p[0] = x_constraint ? min_corner[0] : max_corner[0]; p[1] = y_constraint ? min_corner[1] : max_corner[1]; p[2] = z_constraint ? min_corner[2] : max_corner[2]; p[3] = 1.0f; float err = p.transpose() * Q * p; if (err < best) { best = err; v_new << p[0], p[1], p[2]; } } } } } // Store the dual vertex and voxel grid coordinates dual_vertices[i] = float3{v_new.x(), v_new.y(), v_new.z()}; } end = clock(); if (timing) std::cout << "Dual vertices computation took " << double(end - start) / CLOCKS_PER_SEC << " seconds." << std::endl; return std::make_tuple( torch::from_blob(voxels.data(), {int(voxels .size()), 3}, torch::kInt32).clone(), torch::from_blob(dual_vertices.data(), {int(dual_vertices.size()), 3}, torch::kFloat32).clone(), torch::from_blob(intersected.data(), {int(intersected.size()), 3}, torch::kBool).clone() ); }