conquer3d 0.4.1__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. {conquer3d-0.4.1/conquer3d.egg-info → conquer3d-0.4.2}/PKG-INFO +1 -1
  2. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/_C.so +0 -0
  3. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/__init__.py +6 -0
  4. conquer3d-0.4.2/conquer3d/conversion/tmesh.py +83 -0
  5. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/grid.cpp +71 -0
  6. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +119 -0
  7. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +41 -4
  8. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/mesh_bvh.cu +326 -18
  9. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/mesh_bvh.h +53 -0
  10. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/triangle_mesh.h +4 -0
  11. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/f3x1.h +16 -0
  12. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/aabb.h +1 -1
  13. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/triangle.h +54 -0
  14. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data_structure/__init__.py +2 -0
  15. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data_structure/grid.py +11 -0
  16. {conquer3d-0.4.1 → conquer3d-0.4.2/conquer3d.egg-info}/PKG-INFO +1 -1
  17. {conquer3d-0.4.1 → conquer3d-0.4.2}/pyproject.toml +1 -1
  18. conquer3d-0.4.1/conquer3d/conversion/tmesh.py +0 -49
  19. {conquer3d-0.4.1 → conquer3d-0.4.2}/LICENSE +0 -0
  20. {conquer3d-0.4.1 → conquer3d-0.4.2}/MANIFEST.in +0 -0
  21. {conquer3d-0.4.1 → conquer3d-0.4.2}/README.md +0 -0
  22. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/_C.pyi +0 -0
  23. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/conversion/__init__.py +0 -0
  24. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/conversion/grid.py +0 -0
  25. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/conversion/mesh.py +0 -0
  26. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/creation/__init__.py +0 -0
  27. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/creation/triangle_creation.py +0 -0
  28. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/creation/triangle_creation.cpp +0 -0
  29. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/bvh.cpp +0 -0
  30. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +0 -0
  31. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
  32. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +0 -0
  33. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/zcurve.cpp +0 -0
  34. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/ops/chamfer.cpp +0 -0
  35. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/ops/mc.cpp +0 -0
  36. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
  37. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
  38. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/ray.cpp +0 -0
  39. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/triangle.cpp +0 -0
  40. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/check.h +0 -0
  41. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/constants.h +0 -0
  42. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/creation/triangle_creation.h +0 -0
  43. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/bvh.cu +0 -0
  44. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/bvh.h +0 -0
  45. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
  46. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
  47. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/kdtree.h +0 -0
  48. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
  49. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/triangle_mesh.cu +0 -0
  50. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/zcurve.cu +0 -0
  51. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/zcurve.h +0 -0
  52. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/f2x2.h +0 -0
  53. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/f3x3.h +0 -0
  54. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/f3x4.h +0 -0
  55. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/f4x1.h +0 -0
  56. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/f4x4.h +0 -0
  57. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/maths.h +0 -0
  58. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/maths/ops.h +0 -0
  59. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/ops/chamfer.cu +0 -0
  60. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/ops/chamfer.h +0 -0
  61. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/ops/mc.cu +0 -0
  62. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/ops/mc.h +0 -0
  63. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/ops/mc_data.h +0 -0
  64. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/edge.h +0 -0
  65. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs.cu +0 -0
  66. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs.h +0 -0
  67. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
  68. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
  69. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs.cu +0 -0
  70. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs.h +0 -0
  71. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
  72. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
  73. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/primitive/ray.h +0 -0
  74. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/csrc/pybind.cpp +0 -0
  75. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/__init__.py +0 -0
  76. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/assets/__init__.py +0 -0
  77. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/assets/common.py +0 -0
  78. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/assets/iphigenia.py +0 -0
  79. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/collate/__init__.py +0 -0
  80. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/collate/mesh.py +0 -0
  81. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/collate/sparse_tensor.py +0 -0
  82. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/dataset/__init__.py +0 -0
  83. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/dataset/base_mesh.py +0 -0
  84. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/dataset/digit3d.py +0 -0
  85. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/transform/__init__.py +0 -0
  86. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/transform/base.py +0 -0
  87. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/transform/ops.py +0 -0
  88. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data/transform/vertex.py +0 -0
  89. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data_structure/bmesh.py +0 -0
  90. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/data_structure/sort.py +0 -0
  91. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/io/__init__.py +0 -0
  92. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/io/obj.py +0 -0
  93. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/io/off.py +0 -0
  94. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/ops/__init__.py +0 -0
  95. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/ops/delaunay_triangulation.py +0 -0
  96. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/ops/diff_marching_cubes.py +0 -0
  97. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/ops/distance.py +0 -0
  98. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/ops/marching_cubes.py +0 -0
  99. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/ops/marching_tetrahedra.py +0 -0
  100. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/primitive/__init__.py +0 -0
  101. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/primitive/gs.py +0 -0
  102. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d/primitive/pgs.py +0 -0
  103. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d.egg-info/SOURCES.txt +0 -0
  104. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d.egg-info/dependency_links.txt +0 -0
  105. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d.egg-info/not-zip-safe +0 -0
  106. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d.egg-info/requires.txt +0 -0
  107. {conquer3d-0.4.1 → conquer3d-0.4.2}/conquer3d.egg-info/top_level.txt +0 -0
  108. {conquer3d-0.4.1 → conquer3d-0.4.2}/setup.cfg +0 -0
  109. {conquer3d-0.4.1 → conquer3d-0.4.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conquer3d
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Geometric Cuda Tool Box
5
5
  Author-email: Do Hoang Khoi <khoido8899@gmail.com>
6
6
  License-Expression: MIT
@@ -1,4 +1,10 @@
1
1
  import torch
2
+ import importlib.metadata
3
+
4
+ try:
5
+ __version__ = importlib.metadata.version('conquer3d')
6
+ except importlib.metadata.PackageNotFoundError:
7
+ __version__ = "unknown"
2
8
 
3
9
  from . import _C
4
10
  from . import creation
@@ -0,0 +1,83 @@
1
+ import torch
2
+ import tqdm
3
+ from conquer3d.data_structure.grid import create_voxel_grid, compute_active_voxels
4
+
5
+ def tmesh2voxel(tm, res, grid_min=None, grid_max=None, chunk_size=5000000, device='cuda', show_progress=True):
6
+ """
7
+ Constructs a dense voxel grid from a TriangleMesh and evaluates its SDF.
8
+
9
+ Returns:
10
+ grid_vertices, voxels, idx_grids, sdfs
11
+ """
12
+ if grid_min is None:
13
+ grid_min = [-1.0, -1.0, -1.0]
14
+ if grid_max is None:
15
+ grid_max = [1.0, 1.0, 1.0]
16
+
17
+ if isinstance(res, int):
18
+ res_list = [res, res, res]
19
+ else:
20
+ res_list = list(res)
21
+ grid_vertices, voxels, idx_grids = create_voxel_grid(grid_min, grid_max, res_list, device=device)
22
+
23
+ num_points = grid_vertices.shape[0]
24
+ sdfs = torch.empty(num_points, dtype=torch.float32, device=device)
25
+
26
+ iterator = range(0, num_points, chunk_size)
27
+ if show_progress:
28
+ iterator = tqdm.tqdm(iterator, desc="Computing SDF")
29
+
30
+ for i in iterator:
31
+ end = min(i + chunk_size, num_points)
32
+ chunk_points = grid_vertices[i:end]
33
+ _, _, _, chunk_sdf = tm.query_points(chunk_points, return_sdf=True, return_prj_pts=False, sign_mode=1)
34
+ sdfs[i:end] = chunk_sdf
35
+
36
+ return grid_vertices, voxels, idx_grids, sdfs
37
+
38
+ def tmesh2sparse(tm, res, grid_min=None, grid_max=None, chunk_size=5000000, iso=0.0, device='cuda', show_progress=True):
39
+ """
40
+ Computes the SDF and returns only the active voxels (sparse grid) near the surface.
41
+ This now completely bypasses dense memory allocation!
42
+
43
+ Returns:
44
+ grid_vertices, active_voxels, idx_grids, sdfs
45
+ """
46
+ if grid_min is None:
47
+ grid_min = [-1.0, -1.0, -1.0]
48
+ if grid_max is None:
49
+ grid_max = [1.0, 1.0, 1.0]
50
+
51
+ if isinstance(res, int):
52
+ res_list = [res, res, res]
53
+ else:
54
+ res_list = list(res)
55
+
56
+ from conquer3d.data_structure import create_voxel_grid_from_tmesh
57
+
58
+ if tm.bvh is None:
59
+ tm.build_bvh()
60
+
61
+ grid_vertices, active_voxels, unique_vert_ids = create_voxel_grid_from_tmesh(
62
+ grid_min, grid_max, res_list, tm
63
+ )
64
+
65
+ num_points = grid_vertices.shape[0]
66
+ sdfs = torch.empty(num_points, dtype=torch.float32, device=device)
67
+
68
+ iterator = range(0, num_points, chunk_size)
69
+ if show_progress:
70
+ iterator = tqdm.tqdm(iterator, desc="Computing Sparse SDF")
71
+
72
+ for i in iterator:
73
+ end = min(i + chunk_size, num_points)
74
+ chunk_points = grid_vertices[i:end]
75
+ _, _, _, chunk_sdf = tm.query_points(chunk_points, return_sdf=True, return_prj_pts=False, sign_mode=1)
76
+ sdfs[i:end] = chunk_sdf
77
+
78
+ # Since we strictly extracted surface-intersecting voxels in C++,
79
+ # we don't strictly need to re-filter them, but we can do a quick check to refine the band.
80
+ # However, marching_cubes only uses active_voxels.
81
+ # To keep identical API, we return None for idx_grids since it's dense and unnecessary.
82
+ return grid_vertices, active_voxels.to(torch.int64), None, sdfs
83
+
@@ -4,6 +4,8 @@
4
4
  #include <tuple>
5
5
  #include <string>
6
6
  #include <vector>
7
+ #include "../../data_structure/mesh_bvh.h"
8
+ #include "../../data_structure/triangle_mesh.h"
7
9
 
8
10
  namespace py = pybind11;
9
11
 
@@ -139,9 +141,78 @@ torch::Tensor compute_active_voxels(torch::Tensor voxels, torch::Tensor sdf, flo
139
141
  return active_indices;
140
142
  }
141
143
 
144
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> create_voxel_grid_from_tmesh(
145
+ std::vector<float> grid_min,
146
+ std::vector<float> grid_max,
147
+ std::vector<int64_t> res,
148
+ TriangleMesh &tmesh
149
+ ) {
150
+ TORCH_CHECK(grid_min.size() == 3, "grid_min must have 3 elements.");
151
+ TORCH_CHECK(grid_max.size() == 3, "grid_max must have 3 elements.");
152
+ TORCH_CHECK(res.size() == 3, "res must have 3 elements.");
153
+
154
+ int64_t rx = res[0];
155
+ int64_t ry = res[1];
156
+ int64_t rz = res[2];
157
+
158
+ MeshBVH bvh = tmesh.build_bvh();
159
+ auto vertices = tmesh.get_vertices();
160
+ auto triangles = tmesh.get_triangles();
161
+
162
+ auto active_voxel_ids = bvh.get_active_voxel_ids_from_grid(grid_min, grid_max, res, vertices, triangles);
163
+
164
+ if (active_voxel_ids.size(0) == 0) {
165
+ return std::make_tuple(
166
+ torch::empty({0, 3}, torch::TensorOptions().dtype(torch::kFloat32).device(vertices.device())),
167
+ torch::empty({0, 8}, torch::TensorOptions().dtype(torch::kInt32).device(vertices.device())),
168
+ torch::empty({0}, torch::TensorOptions().dtype(torch::kInt64).device(vertices.device()))
169
+ );
170
+ }
171
+
172
+ auto vi = active_voxel_ids.div((ry - 1) * (rz - 1), "trunc");
173
+ auto rem = active_voxel_ids.remainder((ry - 1) * (rz - 1));
174
+ auto vj = rem.div(rz - 1, "trunc");
175
+ auto vk = rem.remainder(rz - 1);
176
+
177
+ auto v0 = vi * ry * rz + vj * rz + vk;
178
+ auto v1 = (vi + 1) * ry * rz + vj * rz + vk;
179
+ auto v2 = (vi + 1) * ry * rz + (vj + 1) * rz + vk;
180
+ auto v3 = vi * ry * rz + (vj + 1) * rz + vk;
181
+ auto v4 = vi * ry * rz + vj * rz + (vk + 1);
182
+ auto v5 = (vi + 1) * ry * rz + vj * rz + (vk + 1);
183
+ auto v6 = (vi + 1) * ry * rz + (vj + 1) * rz + (vk + 1);
184
+ auto v7 = vi * ry * rz + (vj + 1) * rz + (vk + 1);
185
+
186
+ auto active_voxels = torch::stack({v0, v1, v2, v3, v4, v5, v6, v7}, -1);
187
+
188
+ torch::Tensor unique_vert_ids, inverse_indices, counts;
189
+ std::tie(unique_vert_ids, inverse_indices, counts) = torch::_unique2(active_voxels.flatten(), true, true, false);
190
+
191
+ auto remapped_voxels = inverse_indices.view({-1, 8}).to(torch::kInt32);
192
+
193
+ auto u_i = unique_vert_ids.div(ry * rz, "trunc");
194
+ auto u_rem = unique_vert_ids.remainder(ry * rz);
195
+ auto u_j = u_rem.div(rz, "trunc");
196
+ auto u_k = u_rem.remainder(rz);
197
+
198
+ float spacing_x = (grid_max[0] - grid_min[0]) / (rx - 1);
199
+ float spacing_y = (grid_max[1] - grid_min[1]) / (ry - 1);
200
+ float spacing_z = (grid_max[2] - grid_min[2]) / (rz - 1);
201
+
202
+ auto x = grid_min[0] + u_i.to(torch::kFloat32) * spacing_x;
203
+ auto y = grid_min[1] + u_j.to(torch::kFloat32) * spacing_y;
204
+ auto z = grid_min[2] + u_k.to(torch::kFloat32) * spacing_z;
205
+
206
+ auto sparse_grid_vertices = torch::stack({x, y, z}, 1).contiguous();
207
+
208
+ return std::make_tuple(sparse_grid_vertices, remapped_voxels, unique_vert_ids);
209
+ }
210
+
142
211
  void bind_ds_grid(py::module_& m) {
143
212
  m.def("create_voxel_grid", &create_voxel_grid, "Creates a structured 3D voxel grid.",
144
213
  py::arg("grid_min"), py::arg("grid_max"), py::arg("res"), py::arg("device_str") = "cuda");
214
+ m.def("create_voxel_grid_from_tmesh", &create_voxel_grid_from_tmesh, "Creates a sparse 3D voxel grid strictly around the surface.",
215
+ py::arg("grid_min"), py::arg("grid_max"), py::arg("res"), py::arg("tmesh"));
145
216
  m.def("compute_grid_normal", &compute_grid_normal, "Computes surface normals for a grid.",
146
217
  py::arg("sdf"), py::arg("grid_vertices"), py::arg("idx_grids"), py::arg("res"));
147
218
  m.def("compute_active_voxels", &compute_active_voxels, "Computes active voxels intersecting the surface",
@@ -187,6 +187,112 @@ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> MeshBVH::
187
187
  return std::make_tuple(out_query_ids, out_object_ids, out_projected_pts, out_distances);
188
188
  }
189
189
 
190
+ torch::Tensor MeshBVH::query_voxel(
191
+ const torch::Tensor &query_mins,
192
+ const torch::Tensor &query_maxs,
193
+ const torch::Tensor &vertices,
194
+ const torch::Tensor &triangles)
195
+ {
196
+ int num_queries = query_mins.size(0);
197
+ int num_objects = this->object_ids.size(0);
198
+
199
+ auto options_bool = torch::TensorOptions().dtype(torch::kBool).device(query_mins.device());
200
+ torch::Tensor out_intersect = torch::empty({num_queries}, options_bool);
201
+
202
+ if (num_queries == 0 || num_objects == 0)
203
+ {
204
+ out_intersect.fill_(false);
205
+ return out_intersect;
206
+ }
207
+
208
+ mesh_bvh::query_voxel_mesh_bvh(
209
+ num_queries,
210
+ num_objects,
211
+ (const float3 *)query_mins.data_ptr<float>(),
212
+ (const float3 *)query_maxs.data_ptr<float>(),
213
+ (const float3 *)vertices.data_ptr<float>(),
214
+ (const int3 *)triangles.data_ptr<int>(),
215
+ (const float3 *)this->aabb_mins.data_ptr<float>(),
216
+ (const float3 *)this->aabb_maxs.data_ptr<float>(),
217
+ (const int2 *)this->bvh_children.data_ptr<int>(),
218
+ this->object_ids.data_ptr<int>(),
219
+ out_intersect.data_ptr<bool>());
220
+
221
+ return out_intersect;
222
+ }
223
+
224
+ torch::Tensor MeshBVH::get_active_voxel_ids_from_grid(
225
+ std::vector<float> grid_min,
226
+ std::vector<float> grid_max,
227
+ std::vector<int64_t> res,
228
+ const torch::Tensor &vertices,
229
+ const torch::Tensor &triangles)
230
+ {
231
+ int64_t rx = res[0] - 1;
232
+ int64_t ry = res[1] - 1;
233
+ int64_t rz = res[2] - 1;
234
+ int64_t num_queries = rx * ry * rz;
235
+ int num_objects = this->object_ids.size(0);
236
+
237
+ auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(vertices.device());
238
+
239
+ if (num_queries <= 0 || num_objects == 0)
240
+ {
241
+ return torch::empty({0}, options_i64);
242
+ }
243
+
244
+ float spacing_x = (grid_max[0] - grid_min[0]) / (res[0] - 1);
245
+ float spacing_y = (grid_max[1] - grid_min[1]) / (res[1] - 1);
246
+ float spacing_z = (grid_max[2] - grid_min[2]) / (res[2] - 1);
247
+
248
+ int3 res_int = make_int3(rx, ry, rz);
249
+ float3 g_min = make_float3(grid_min[0], grid_min[1], grid_min[2]);
250
+ float3 v_size = make_float3(spacing_x, spacing_y, spacing_z);
251
+
252
+ // Pass 1: Count active voxels
253
+ torch::Tensor active_counter = torch::zeros({1}, options_i64);
254
+
255
+ mesh_bvh::count_active_voxels_mesh_bvh(
256
+ res_int,
257
+ g_min,
258
+ v_size,
259
+ num_objects,
260
+ (const float3 *)vertices.data_ptr<float>(),
261
+ (const int3 *)triangles.data_ptr<int>(),
262
+ (const float3 *)this->aabb_mins.data_ptr<float>(),
263
+ (const float3 *)this->aabb_maxs.data_ptr<float>(),
264
+ (const int2 *)this->bvh_children.data_ptr<int>(),
265
+ this->object_ids.data_ptr<int>(),
266
+ active_counter.data_ptr<int64_t>());
267
+
268
+ int64_t num_active = active_counter.item<int64_t>();
269
+
270
+ if (num_active == 0)
271
+ {
272
+ return torch::empty({0}, options_i64);
273
+ }
274
+
275
+ // Pass 2: Collect active voxels
276
+ torch::Tensor out_active_ids = torch::empty({num_active}, options_i64);
277
+ active_counter.zero_();
278
+
279
+ mesh_bvh::collect_active_voxels_mesh_bvh(
280
+ res_int,
281
+ g_min,
282
+ v_size,
283
+ num_objects,
284
+ (const float3 *)vertices.data_ptr<float>(),
285
+ (const int3 *)triangles.data_ptr<int>(),
286
+ (const float3 *)this->aabb_mins.data_ptr<float>(),
287
+ (const float3 *)this->aabb_maxs.data_ptr<float>(),
288
+ (const int2 *)this->bvh_children.data_ptr<int>(),
289
+ this->object_ids.data_ptr<int>(),
290
+ active_counter.data_ptr<int64_t>(),
291
+ out_active_ids.data_ptr<int64_t>());
292
+
293
+ return out_active_ids;
294
+ }
295
+
190
296
  void MeshBVH::build_winding_data(
191
297
  const torch::Tensor &vertices,
192
298
  const torch::Tensor &triangles)
@@ -295,5 +401,18 @@ void bind_ds_mesh_bvh(py::module_ &m)
295
401
  Args:
296
402
  vertices (torch.Tensor): Shape (N, 3) float32 tensor of vertices.
297
403
  triangles (torch.Tensor): Shape (M, 3) int32 tensor of triangles.
404
+ )doc")
405
+ .def("query_voxel", &MeshBVH::query_voxel, py::arg("query_mins"), py::arg("query_maxs"), py::arg("vertices"), py::arg("triangles"),
406
+ R"doc(
407
+ Queries whether each voxel intersects the mesh.
408
+
409
+ Args:
410
+ query_mins (torch.Tensor): Shape (Q, 3) float32 tensor of voxel minimums.
411
+ query_maxs (torch.Tensor): Shape (Q, 3) float32 tensor of voxel maximums.
412
+ vertices (torch.Tensor): Shape (N, 3) float32 tensor of vertices.
413
+ triangles (torch.Tensor): Shape (M, 3) int32 tensor of triangles.
414
+
415
+ Returns:
416
+ torch.Tensor: Boolean tensor of shape (Q,) indicating intersection.
298
417
  )doc");
299
418
  }
@@ -296,6 +296,10 @@ void TriangleMesh::remove_isolated_vertices() {
296
296
  this->triangle_normals = torch::Tensor();
297
297
  this->surface_area = torch::Tensor();
298
298
  this->bvh.reset();
299
+ this->opt_edge_manifold = std::nullopt;
300
+ this->opt_edge_manifold_w_boundary = std::nullopt;
301
+ this->opt_vertex_manifold = std::nullopt;
302
+ this->opt_self_intersected = std::nullopt;
299
303
  this->edges = torch::Tensor();
300
304
  this->edge_to_triangle_offsets = torch::Tensor();
301
305
  this->edge_to_triangle_counts = torch::Tensor();
@@ -347,8 +351,13 @@ torch::Tensor TriangleMesh::get_self_intersection()
347
351
 
348
352
  bool TriangleMesh::is_self_intersection()
349
353
  {
354
+ if (this->opt_self_intersected.has_value()) {
355
+ return this->opt_self_intersected.value();
356
+ }
350
357
  this->build_bvh();
351
- return this->bvh.value().is_self_intersection(this->vertices, this->triangles);
358
+ bool self_int = this->bvh.value().is_self_intersection(this->vertices, this->triangles);
359
+ this->opt_self_intersected = self_int;
360
+ return self_int;
352
361
  }
353
362
 
354
363
  std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::query_points(
@@ -358,6 +367,10 @@ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleM
358
367
  int sign_mode,
359
368
  int distance_mode)
360
369
  {
370
+ if (!this->is_manifold(true)) {
371
+ sign_mode = 0;
372
+ }
373
+
361
374
  if (distance_mode == 0)
362
375
  {
363
376
  this->build_bvh();
@@ -541,15 +554,26 @@ bool TriangleMesh::is_edge_manifold(bool allow_boundary_edge)
541
554
  {
542
555
  if (this->num_triangles == 0)
543
556
  return true;
557
+
558
+ if (allow_boundary_edge && this->opt_edge_manifold_w_boundary.has_value()) {
559
+ return this->opt_edge_manifold_w_boundary.value();
560
+ } else if (!allow_boundary_edge && this->opt_edge_manifold.has_value()) {
561
+ return this->opt_edge_manifold.value();
562
+ }
563
+
544
564
  torch::Tensor counts = this->get_edge_to_triangle_counts();
565
+ bool is_manifold;
545
566
  if (allow_boundary_edge)
546
567
  {
547
- return (counts <= 2).all().item<bool>();
568
+ is_manifold = (counts <= 2).all().item<bool>();
569
+ this->opt_edge_manifold_w_boundary = is_manifold;
548
570
  }
549
571
  else
550
572
  {
551
- return (counts == 2).all().item<bool>();
573
+ is_manifold = (counts == 2).all().item<bool>();
574
+ this->opt_edge_manifold = is_manifold;
552
575
  }
576
+ return is_manifold;
553
577
  }
554
578
 
555
579
  void TriangleMesh::remove_triangles_by_mask(const torch::Tensor &keep_mask)
@@ -566,6 +590,10 @@ void TriangleMesh::remove_triangles_by_mask(const torch::Tensor &keep_mask)
566
590
  this->triangle_normals = torch::Tensor();
567
591
  this->surface_area = torch::Tensor();
568
592
  this->bvh.reset();
593
+ this->opt_edge_manifold = std::nullopt;
594
+ this->opt_edge_manifold_w_boundary = std::nullopt;
595
+ this->opt_vertex_manifold = std::nullopt;
596
+ this->opt_self_intersected = std::nullopt;
569
597
  this->edges = torch::Tensor();
570
598
  this->edge_to_triangle_offsets = torch::Tensor();
571
599
  this->edge_to_triangle_counts = torch::Tensor();
@@ -602,6 +630,10 @@ void TriangleMesh::fix_normals()
602
630
  this->triangle_normals = torch::Tensor();
603
631
  this->vertex_normals = torch::Tensor();
604
632
  this->bvh.reset();
633
+ this->opt_edge_manifold = std::nullopt;
634
+ this->opt_edge_manifold_w_boundary = std::nullopt;
635
+ this->opt_vertex_manifold = std::nullopt;
636
+ this->opt_self_intersected = std::nullopt;
605
637
  }
606
638
 
607
639
  int32_t TriangleMesh::get_euler_characteristic()
@@ -671,8 +703,13 @@ torch::Tensor TriangleMesh::get_non_manifold_vertices()
671
703
 
672
704
  bool TriangleMesh::is_vertex_manifold()
673
705
  {
706
+ if (this->opt_vertex_manifold.has_value()) {
707
+ return this->opt_vertex_manifold.value();
708
+ }
674
709
  torch::Tensor nm_vertices = this->get_non_manifold_vertices();
675
- return nm_vertices.size(0) == 0;
710
+ bool is_manifold = (nm_vertices.size(0) == 0);
711
+ this->opt_vertex_manifold = is_manifold;
712
+ return is_manifold;
676
713
  }
677
714
 
678
715
  bool TriangleMesh::is_manifold(bool allow_boundary_edge)