conquer3d 0.4.0__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 (108) hide show
  1. {conquer3d-0.4.0/conquer3d.egg-info → conquer3d-0.4.2}/PKG-INFO +1 -1
  2. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/_C.so +0 -0
  3. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/__init__.py +6 -0
  4. conquer3d-0.4.2/conquer3d/conversion/tmesh.py +83 -0
  5. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/grid.cpp +71 -0
  6. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +119 -0
  7. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +69 -4
  8. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/mesh_bvh.cu +326 -18
  9. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/mesh_bvh.h +53 -0
  10. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/triangle_mesh.cu +192 -0
  11. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/triangle_mesh.h +14 -0
  12. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/f3x1.h +16 -0
  13. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/aabb.h +1 -1
  14. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/triangle.h +54 -0
  15. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data_structure/__init__.py +2 -0
  16. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data_structure/grid.py +11 -0
  17. {conquer3d-0.4.0 → conquer3d-0.4.2/conquer3d.egg-info}/PKG-INFO +1 -1
  18. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d.egg-info/SOURCES.txt +1 -0
  19. {conquer3d-0.4.0 → conquer3d-0.4.2}/pyproject.toml +1 -1
  20. {conquer3d-0.4.0 → conquer3d-0.4.2}/setup.py +6 -4
  21. {conquer3d-0.4.0 → conquer3d-0.4.2}/LICENSE +0 -0
  22. {conquer3d-0.4.0 → conquer3d-0.4.2}/MANIFEST.in +0 -0
  23. {conquer3d-0.4.0 → conquer3d-0.4.2}/README.md +0 -0
  24. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/_C.pyi +0 -0
  25. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/conversion/__init__.py +0 -0
  26. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/conversion/grid.py +0 -0
  27. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/conversion/mesh.py +0 -0
  28. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/creation/__init__.py +0 -0
  29. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/creation/triangle_creation.py +0 -0
  30. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/creation/triangle_creation.cpp +0 -0
  31. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/bvh.cpp +0 -0
  32. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +0 -0
  33. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
  34. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +0 -0
  35. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/data_structure/zcurve.cpp +0 -0
  36. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/ops/chamfer.cpp +0 -0
  37. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/ops/mc.cpp +0 -0
  38. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
  39. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
  40. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/ray.cpp +0 -0
  41. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/binds/primitive/triangle.cpp +0 -0
  42. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/check.h +0 -0
  43. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/constants.h +0 -0
  44. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/creation/triangle_creation.h +0 -0
  45. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/bvh.cu +0 -0
  46. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/bvh.h +0 -0
  47. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
  48. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
  49. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/kdtree.h +0 -0
  50. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
  51. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/zcurve.cu +0 -0
  52. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/data_structure/zcurve.h +0 -0
  53. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/f2x2.h +0 -0
  54. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/f3x3.h +0 -0
  55. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/f3x4.h +0 -0
  56. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/f4x1.h +0 -0
  57. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/f4x4.h +0 -0
  58. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/maths.h +0 -0
  59. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/maths/ops.h +0 -0
  60. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/ops/chamfer.cu +0 -0
  61. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/ops/chamfer.h +0 -0
  62. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/ops/mc.cu +0 -0
  63. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/ops/mc.h +0 -0
  64. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/ops/mc_data.h +0 -0
  65. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/edge.h +0 -0
  66. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs.cu +0 -0
  67. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs.h +0 -0
  68. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
  69. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
  70. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs.cu +0 -0
  71. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs.h +0 -0
  72. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
  73. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
  74. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/primitive/ray.h +0 -0
  75. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/csrc/pybind.cpp +0 -0
  76. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/__init__.py +0 -0
  77. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/assets/__init__.py +0 -0
  78. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/assets/common.py +0 -0
  79. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/assets/iphigenia.py +0 -0
  80. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/collate/__init__.py +0 -0
  81. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/collate/mesh.py +0 -0
  82. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/collate/sparse_tensor.py +0 -0
  83. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/dataset/__init__.py +0 -0
  84. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/dataset/base_mesh.py +0 -0
  85. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/dataset/digit3d.py +0 -0
  86. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/transform/__init__.py +0 -0
  87. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/transform/base.py +0 -0
  88. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/transform/ops.py +0 -0
  89. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data/transform/vertex.py +0 -0
  90. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data_structure/bmesh.py +0 -0
  91. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/data_structure/sort.py +0 -0
  92. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/io/__init__.py +0 -0
  93. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/io/obj.py +0 -0
  94. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/io/off.py +0 -0
  95. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/ops/__init__.py +0 -0
  96. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/ops/delaunay_triangulation.py +0 -0
  97. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/ops/diff_marching_cubes.py +0 -0
  98. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/ops/distance.py +0 -0
  99. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/ops/marching_cubes.py +0 -0
  100. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/ops/marching_tetrahedra.py +0 -0
  101. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/primitive/__init__.py +0 -0
  102. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/primitive/gs.py +0 -0
  103. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d/primitive/pgs.py +0 -0
  104. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d.egg-info/dependency_links.txt +0 -0
  105. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d.egg-info/not-zip-safe +0 -0
  106. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d.egg-info/requires.txt +0 -0
  107. {conquer3d-0.4.0 → conquer3d-0.4.2}/conquer3d.egg-info/top_level.txt +0 -0
  108. {conquer3d-0.4.0 → conquer3d-0.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conquer3d
3
- Version: 0.4.0
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();
@@ -581,6 +609,33 @@ void TriangleMesh::remove_triangles_by_mask(const torch::Tensor &keep_mask)
581
609
  this->voronoi_areas = torch::Tensor();
582
610
  }
583
611
 
612
+ void TriangleMesh::fix_normals()
613
+ {
614
+ if (this->num_triangles == 0) return;
615
+
616
+ if (!this->vertex_to_triangle_offsets.defined()) {
617
+ this->compute_vertices_to_triangle_map();
618
+ }
619
+
620
+ triangle_mesh::fix_normals(
621
+ this->num_triangles,
622
+ reinterpret_cast<const float3 *>(this->vertices.data_ptr<float>()),
623
+ this->vertex_to_triangle_offsets,
624
+ this->vertex_to_triangle_counts,
625
+ this->vertex_to_triangle_indices,
626
+ reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()));
627
+
628
+ // Invalidate caches
629
+ this->triangle_areas = torch::Tensor();
630
+ this->triangle_normals = torch::Tensor();
631
+ this->vertex_normals = torch::Tensor();
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;
637
+ }
638
+
584
639
  int32_t TriangleMesh::get_euler_characteristic()
585
640
  {
586
641
  int32_t V = this->vertices.size(0);
@@ -648,8 +703,13 @@ torch::Tensor TriangleMesh::get_non_manifold_vertices()
648
703
 
649
704
  bool TriangleMesh::is_vertex_manifold()
650
705
  {
706
+ if (this->opt_vertex_manifold.has_value()) {
707
+ return this->opt_vertex_manifold.value();
708
+ }
651
709
  torch::Tensor nm_vertices = this->get_non_manifold_vertices();
652
- 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;
653
713
  }
654
714
 
655
715
  bool TriangleMesh::is_manifold(bool allow_boundary_edge)
@@ -934,6 +994,11 @@ void bind_ds_triangle_mesh(py::module_ &m)
934
994
  Args:
935
995
  keep_mask (torch.Tensor): Shape (M,) boolean tensor indicating which triangles to keep.
936
996
  )doc")
997
+ .def("fix_normals", &TriangleMesh::fix_normals, R"doc(
998
+ Fixes the winding order and outward orientation of the mesh normals.
999
+ This uses a CUDA-accelerated BFS to ensure consistent winding,
1000
+ and computes signed volumes to ensure all disconnected components face outward.
1001
+ )doc")
937
1002
  .def("sample_points", &TriangleMesh::sample_points, py::arg("num_points"), py::arg("uniform") = false, py::arg("return_normals") = false, py::arg("return_colors") = false, py::arg("use_triangle_normal") = true, R"doc(
938
1003
  Samples random points on the surface of the mesh.
939
1004