conquer3d 0.2.5__tar.gz → 0.2.7__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 (69) hide show
  1. {conquer3d-0.2.5/conquer3d.egg-info → conquer3d-0.2.7}/PKG-INFO +1 -1
  2. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/_C.pyi +24 -2
  3. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/_C.so +0 -0
  4. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/data_structure/bvh.cpp +69 -32
  5. conquer3d-0.2.7/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +238 -0
  6. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +111 -2
  7. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/bvh.cu +93 -0
  8. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/bvh.h +17 -1
  9. conquer3d-0.2.7/conquer3d/csrc/data_structure/mesh_bvh.cu +547 -0
  10. conquer3d-0.2.7/conquer3d/csrc/data_structure/mesh_bvh.h +112 -0
  11. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/triangle_mesh.cu +151 -0
  12. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/triangle_mesh.h +36 -1
  13. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/aabb.h +18 -0
  14. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/triangles.h +57 -0
  15. {conquer3d-0.2.5 → conquer3d-0.2.7/conquer3d.egg-info}/PKG-INFO +1 -1
  16. {conquer3d-0.2.5 → conquer3d-0.2.7}/pyproject.toml +1 -1
  17. conquer3d-0.2.5/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +0 -36
  18. conquer3d-0.2.5/conquer3d/csrc/data_structure/mesh_bvh.cu +0 -205
  19. conquer3d-0.2.5/conquer3d/csrc/data_structure/mesh_bvh.h +0 -33
  20. {conquer3d-0.2.5 → conquer3d-0.2.7}/LICENSE +0 -0
  21. {conquer3d-0.2.5 → conquer3d-0.2.7}/MANIFEST.in +0 -0
  22. {conquer3d-0.2.5 → conquer3d-0.2.7}/README.md +0 -0
  23. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/__init__.py +0 -0
  24. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/creation/__init__.py +0 -0
  25. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/creation/triangle_creation.py +0 -0
  26. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/creation/triangle_creation.cpp +0 -0
  27. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +0 -0
  28. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
  29. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +0 -0
  30. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
  31. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
  32. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/check.h +0 -0
  33. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/constants.h +0 -0
  34. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/creation/triangle_creation.h +0 -0
  35. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
  36. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
  37. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/kdtree.h +0 -0
  38. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
  39. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/data_structure/zcurve.h +0 -0
  40. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/f2x2.h +0 -0
  41. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/f3x1.h +0 -0
  42. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/f3x3.h +0 -0
  43. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/f3x4.h +0 -0
  44. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/f4x1.h +0 -0
  45. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/f4x4.h +0 -0
  46. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/maths.h +0 -0
  47. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/maths/ops.h +0 -0
  48. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/edge.h +0 -0
  49. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/gs.cu +0 -0
  50. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/gs.h +0 -0
  51. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
  52. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
  53. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/pgs.cu +0 -0
  54. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/pgs.h +0 -0
  55. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
  56. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
  57. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/primitive/ray.h +0 -0
  58. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/csrc/pybind.cpp +0 -0
  59. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/data_structure/__init__.py +0 -0
  60. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/primitive/__init__.py +0 -0
  61. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/primitive/gs.py +0 -0
  62. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d/primitive/pgs.py +0 -0
  63. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d.egg-info/SOURCES.txt +0 -0
  64. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d.egg-info/dependency_links.txt +0 -0
  65. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d.egg-info/not-zip-safe +0 -0
  66. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d.egg-info/requires.txt +0 -0
  67. {conquer3d-0.2.5 → conquer3d-0.2.7}/conquer3d.egg-info/top_level.txt +0 -0
  68. {conquer3d-0.2.5 → conquer3d-0.2.7}/setup.cfg +0 -0
  69. {conquer3d-0.2.5 → conquer3d-0.2.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conquer3d
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Geometric Cuda Tool Box
5
5
  Author-email: Do Hoang Khoi <khoido8899@gmail.com>
6
6
  License-Expression: MIT
@@ -14,9 +14,13 @@ class BVH:
14
14
  """
15
15
  Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).
16
16
  """
17
- def query_ray(self, ray_origins: torch.Tensor, ray_dirs: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
17
+ def query_point(self, query_points: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
18
18
  """
19
- Query the BVH with rays. Returns (query_ids, object_ids).
19
+ Find the closest leaf AABB to each query point. Returns (query_ids, object_ids, distances)
20
+ """
21
+ def query_ray(self, ray_origins: torch.Tensor, ray_dirs: torch.Tensor, max_capacity: int = 10000000) -> tuple[torch.Tensor, torch.Tensor]:
22
+ """
23
+ Find all ray-AABB intersections. Returns (ray_ids, object_ids)
20
24
  """
21
25
  def query_self(self) -> tuple[torch.Tensor, torch.Tensor]:
22
26
  """
@@ -53,6 +57,10 @@ class MeshBVH(BVH):
53
57
  """
54
58
  Construct MeshBVH from Triangle AABBs.
55
59
  """
60
+ def build_winding_data(self, vertices: torch.Tensor, triangles: torch.Tensor) -> None:
61
+ """
62
+ Computes the hierarchical area-weighted constants for Fast Winding Number queries.
63
+ """
56
64
  def get_ray_intersection(self, ray_origins: torch.Tensor, ray_dirs: torch.Tensor, vertices: torch.Tensor, triangles: torch.Tensor, return_distance: bool = False) -> typing.Any:
57
65
  """
58
66
  Find all ray-triangle intersections. Returns (ray_ids, triangle_ids, intersect_points, [distances])
@@ -65,6 +73,10 @@ class MeshBVH(BVH):
65
73
  """
66
74
  Check if there are any self-intersecting triangle pairs
67
75
  """
76
+ def query_point(self, query_points: torch.Tensor, vertices: torch.Tensor, triangles: torch.Tensor, return_sdf: bool = False, return_prj_pts: bool = True, sign_mode: int = 0) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
77
+ """
78
+ Find the closest triangle to each query point. Returns (query_ids, triangle_ids, projected_points, distances)
79
+ """
68
80
  class PGSBVH(BVH):
69
81
  def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
70
82
  """
@@ -89,6 +101,8 @@ class TriangleMesh:
89
101
  ...
90
102
  def compute_triangle_normals(self) -> None:
91
103
  ...
104
+ def compute_vertex_normals(self) -> None:
105
+ ...
92
106
  def compute_vertices_to_triangle_map(self) -> None:
93
107
  ...
94
108
  def get_edges_to_triangle_map(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
@@ -115,8 +129,16 @@ class TriangleMesh:
115
129
  """
116
130
  def is_vertex_manifold(self) -> bool:
117
131
  ...
132
+ def query_points(self, query_pts: torch.Tensor, return_sdf: bool = False, return_prj_pts: bool = True, sign_mode: int = 0, distance_mode: int = 0) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
133
+ """
134
+ Query points against the mesh. Returns (query_ids, triangle_ids, projected_points, distances)
135
+ """
118
136
  def remove_triangles_by_mask(self, keep_mask: torch.Tensor) -> None:
119
137
  ...
138
+ def sample_points(self, num_points: int, uniform: bool = False, return_normals: bool = False, return_colors: bool = False, use_triangle_normal: bool = True) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
139
+ """
140
+ Sample points on the mesh
141
+ """
120
142
  @property
121
143
  def bvh(self) -> MeshBVH:
122
144
  ...
@@ -155,48 +155,81 @@ std::tuple<torch::Tensor, torch::Tensor> BVH::query_self()
155
155
 
156
156
  std::tuple<torch::Tensor, torch::Tensor> BVH::query_ray(
157
157
  const torch::Tensor &ray_origins,
158
- const torch::Tensor &ray_dirs)
158
+ const torch::Tensor &ray_dirs,
159
+ int64_t max_capacity)
159
160
  {
160
161
  CHECK_INPUT(ray_origins);
161
162
  CHECK_INPUT(ray_dirs);
162
- TORCH_CHECK(ray_origins.scalar_type() == torch::kFloat32, "ray_origins must be float32");
163
- TORCH_CHECK(ray_dirs.scalar_type() == torch::kFloat32, "ray_dirs must be float32");
164
- TORCH_CHECK(ray_origins.size(1) == 3, "ray_origins must have shape (M, 3)");
165
- TORCH_CHECK(ray_dirs.size(1) == 3, "ray_dirs must have shape (M, 3)");
163
+ TORCH_CHECK(ray_origins.size(0) == ray_dirs.size(0), "ray_origins and ray_dirs must have the same number of rays");
166
164
 
167
- const uint32_t num_queries = static_cast<uint32_t>(ray_origins.size(0));
168
- auto options_int64 = ray_origins.options().dtype(torch::kInt64);
165
+ int num_queries = ray_origins.size(0);
169
166
 
170
- if (num_queries == 0) {
171
- throw std::runtime_error("Cannot query BVH with 0 queries.");
172
- }
173
-
174
- torch::Tensor out_query_ids = torch::empty({BVH_MAX_CAPACITY}, options_int64);
175
- torch::Tensor out_object_ids = torch::empty({BVH_MAX_CAPACITY}, options_int64);
176
- torch::Tensor hit_counter = torch::zeros({1}, options_int64);
167
+ auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(ray_origins.device());
168
+ torch::Tensor out_query_ids = torch::empty({max_capacity}, options_i64);
169
+ torch::Tensor out_object_ids = torch::empty({max_capacity}, options_i64);
170
+ torch::Tensor hit_counter = torch::zeros({1}, options_i64);
177
171
 
178
172
  bvh::query_ray(
179
173
  num_queries,
180
174
  this->num_objects,
181
- reinterpret_cast<const float3 *>(ray_origins.data_ptr<float>()),
182
- reinterpret_cast<const float3 *>(ray_dirs.data_ptr<float>()),
183
- reinterpret_cast<const float3 *>(this->aabb_mins.data_ptr<float>()),
184
- reinterpret_cast<const float3 *>(this->aabb_maxs.data_ptr<float>()),
185
- reinterpret_cast<const int2 *>(this->bvh_children.data_ptr<int>()),
186
- reinterpret_cast<const int *>(this->object_ids.data_ptr<int>()),
187
- reinterpret_cast<int64_t *>(out_query_ids.data_ptr<int64_t>()),
188
- reinterpret_cast<int64_t *>(out_object_ids.data_ptr<int64_t>()),
189
- reinterpret_cast<int64_t *>(hit_counter.data_ptr<int64_t>()),
190
- static_cast<int64_t>(BVH_MAX_CAPACITY)
191
- );
175
+ (const float3 *)ray_origins.data_ptr<float>(),
176
+ (const float3 *)ray_dirs.data_ptr<float>(),
177
+ (const float3 *)this->aabb_mins.data_ptr<float>(),
178
+ (const float3 *)this->aabb_maxs.data_ptr<float>(),
179
+ (const int2 *)this->bvh_children.data_ptr<int>(),
180
+ this->object_ids.data_ptr<int>(),
181
+ out_query_ids.data_ptr<int64_t>(),
182
+ out_object_ids.data_ptr<int64_t>(),
183
+ hit_counter.data_ptr<int64_t>(),
184
+ max_capacity);
185
+
186
+ int64_t h_hit_counter = hit_counter.item<int64_t>();
187
+
188
+ if (h_hit_counter >= max_capacity)
189
+ {
190
+ TORCH_WARN("BVH ray query capacity exceeded. Some hits were dropped.");
191
+ h_hit_counter = max_capacity;
192
+ }
192
193
 
193
- int64_t num_hits = hit_counter.item<int64_t>();
194
- num_hits = std::min(num_hits, static_cast<int64_t>(BVH_MAX_CAPACITY));
194
+ if (h_hit_counter == 0) {
195
+ return std::make_tuple(
196
+ torch::empty({0}, options_i64),
197
+ torch::empty({0}, options_i64)
198
+ );
199
+ }
195
200
 
196
201
  return std::make_tuple(
197
- out_query_ids.slice(0, 0, num_hits),
198
- out_object_ids.slice(0, 0, num_hits)
199
- );
202
+ out_query_ids.slice(0, 0, h_hit_counter),
203
+ out_object_ids.slice(0, 0, h_hit_counter));
204
+ }
205
+
206
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> BVH::query_point(
207
+ const torch::Tensor &query_points)
208
+ {
209
+ CHECK_INPUT(query_points);
210
+
211
+ int num_queries = query_points.size(0);
212
+
213
+ auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(query_points.device());
214
+ auto options_f32 = torch::TensorOptions().dtype(torch::kFloat32).device(query_points.device());
215
+
216
+ torch::Tensor out_query_ids = torch::empty({num_queries}, options_i64);
217
+ torch::Tensor out_object_ids = torch::empty({num_queries}, options_i64);
218
+ torch::Tensor out_distances = torch::empty({num_queries}, options_f32);
219
+
220
+ bvh::query_point(
221
+ num_queries,
222
+ this->num_objects,
223
+ (const float3 *)query_points.data_ptr<float>(),
224
+ (const float3 *)this->aabb_mins.data_ptr<float>(),
225
+ (const float3 *)this->aabb_maxs.data_ptr<float>(),
226
+ (const int2 *)this->bvh_children.data_ptr<int>(),
227
+ this->object_ids.data_ptr<int>(),
228
+ out_query_ids.data_ptr<int64_t>(),
229
+ out_object_ids.data_ptr<int64_t>(),
230
+ out_distances.data_ptr<float>());
231
+
232
+ return std::make_tuple(out_query_ids, out_object_ids, out_distances);
200
233
  }
201
234
 
202
235
  void bind_ds_bvh(py::module_& m)
@@ -216,7 +249,11 @@ void bind_ds_bvh(py::module_& m)
216
249
  "Query the BVH against itself. Returns unique overlapping (query_ids, object_ids).")
217
250
 
218
251
  .def("query_ray", &BVH::query_ray,
219
- py::arg("ray_origins"),
252
+ py::arg("ray_origins"),
220
253
  py::arg("ray_dirs"),
221
- "Query the BVH with rays. Returns (query_ids, object_ids).");
254
+ py::arg("max_capacity") = BVH_MAX_CAPACITY,
255
+ "Find all ray-AABB intersections. Returns (ray_ids, object_ids)")
256
+ .def("query_point", &BVH::query_point,
257
+ py::arg("query_points"),
258
+ "Find the closest leaf AABB to each query point. Returns (query_ids, object_ids, distances)");
222
259
  }
@@ -0,0 +1,238 @@
1
+ #include <torch/extension.h>
2
+ #include "../../data_structure/mesh_bvh.h"
3
+ #include <pybind11/pybind11.h>
4
+ #include "../../check.h"
5
+
6
+ namespace py = pybind11;
7
+
8
+ torch::Tensor MeshBVH::get_self_intersection(
9
+ const torch::Tensor &vertices,
10
+ const torch::Tensor &triangles)
11
+ {
12
+ // 1. Broad-phase AABB overlap query
13
+ auto [query_ids, object_ids] = this->query_self();
14
+
15
+ int num_pairs = query_ids.size(0);
16
+ if (num_pairs == 0)
17
+ {
18
+ return torch::empty({0, 2}, torch::TensorOptions().dtype(torch::kInt64).device(query_ids.device()));
19
+ }
20
+
21
+ // 2. Narrow-phase intersection
22
+ auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(query_ids.device());
23
+ torch::Tensor out_query_ids = torch::empty({num_pairs}, options_i64);
24
+ torch::Tensor out_object_ids = torch::empty({num_pairs}, options_i64);
25
+ torch::Tensor valid_counter = torch::zeros({1}, options_i64);
26
+
27
+ mesh_bvh::filter_self_intersections(
28
+ num_pairs,
29
+ query_ids.data_ptr<int64_t>(),
30
+ object_ids.data_ptr<int64_t>(),
31
+ (const float3 *)vertices.data_ptr<float>(),
32
+ (const int3 *)triangles.data_ptr<int>(),
33
+ out_query_ids.data_ptr<int64_t>(),
34
+ out_object_ids.data_ptr<int64_t>(),
35
+ valid_counter.data_ptr<int64_t>());
36
+
37
+ int64_t h_valid_counter = valid_counter.item<int64_t>();
38
+
39
+ if (h_valid_counter == 0)
40
+ {
41
+ return torch::empty({0, 2}, options_i64);
42
+ }
43
+
44
+ out_query_ids = out_query_ids.slice(0, 0, h_valid_counter).unsqueeze(1);
45
+ out_object_ids = out_object_ids.slice(0, 0, h_valid_counter).unsqueeze(1);
46
+
47
+ return torch::cat({out_query_ids, out_object_ids}, 1);
48
+ }
49
+
50
+ bool MeshBVH::is_self_intersection(
51
+ const torch::Tensor &vertices,
52
+ const torch::Tensor &triangles)
53
+ {
54
+ torch::Tensor pairs = this->get_self_intersection(vertices, triangles);
55
+ return pairs.size(0) > 0;
56
+ }
57
+
58
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> MeshBVH::get_ray_intersection(
59
+ const torch::Tensor &ray_origins,
60
+ const torch::Tensor &ray_dirs,
61
+ const torch::Tensor &vertices,
62
+ const torch::Tensor &triangles,
63
+ bool return_distance)
64
+ {
65
+ // 1. Broad-phase BVH query
66
+ auto [query_ids, object_ids] = this->query_ray(ray_origins, ray_dirs);
67
+
68
+ int num_pairs = query_ids.size(0);
69
+ auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(query_ids.device());
70
+ auto options_f32 = torch::TensorOptions().dtype(torch::kFloat32).device(query_ids.device());
71
+
72
+ if (num_pairs == 0)
73
+ {
74
+ return std::make_tuple(
75
+ torch::empty({0}, options_i64),
76
+ torch::empty({0}, options_i64),
77
+ torch::empty({0, 3}, options_f32),
78
+ torch::empty({0}, options_f32));
79
+ }
80
+
81
+ // 2. Narrow-phase intersection
82
+ torch::Tensor out_query_ids = torch::empty({num_pairs}, options_i64);
83
+ torch::Tensor out_object_ids = torch::empty({num_pairs}, options_i64);
84
+ torch::Tensor out_intersect_pts = torch::empty({num_pairs, 3}, options_f32);
85
+
86
+ torch::Tensor out_distances;
87
+ if (return_distance)
88
+ {
89
+ out_distances = torch::empty({num_pairs}, options_f32);
90
+ }
91
+ else
92
+ {
93
+ out_distances = torch::empty({0}, options_f32);
94
+ }
95
+
96
+ torch::Tensor valid_counter = torch::zeros({1}, options_i64);
97
+
98
+ mesh_bvh::filter_ray_triangle_intersections(
99
+ num_pairs,
100
+ query_ids.data_ptr<int64_t>(),
101
+ object_ids.data_ptr<int64_t>(),
102
+ (const float3 *)ray_origins.data_ptr<float>(),
103
+ (const float3 *)ray_dirs.data_ptr<float>(),
104
+ (const float3 *)vertices.data_ptr<float>(),
105
+ (const int3 *)triangles.data_ptr<int>(),
106
+ out_query_ids.data_ptr<int64_t>(),
107
+ out_object_ids.data_ptr<int64_t>(),
108
+ (float3 *)out_intersect_pts.data_ptr<float>(),
109
+ return_distance ? out_distances.data_ptr<float>() : nullptr,
110
+ return_distance,
111
+ valid_counter.data_ptr<int64_t>());
112
+
113
+ int64_t h_valid_counter = valid_counter.item<int64_t>();
114
+
115
+ if (h_valid_counter == 0)
116
+ {
117
+ return std::make_tuple(
118
+ torch::empty({0}, options_i64),
119
+ torch::empty({0}, options_i64),
120
+ torch::empty({0, 3}, options_f32),
121
+ torch::empty({0}, options_f32));
122
+ }
123
+
124
+ return std::make_tuple(
125
+ out_query_ids.slice(0, 0, h_valid_counter),
126
+ out_object_ids.slice(0, 0, h_valid_counter),
127
+ out_intersect_pts.slice(0, 0, h_valid_counter),
128
+ return_distance ? out_distances.slice(0, 0, h_valid_counter) : out_distances);
129
+ }
130
+
131
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> MeshBVH::query_point(
132
+ const torch::Tensor &query_points,
133
+ const torch::Tensor &vertices,
134
+ const torch::Tensor &triangles,
135
+ bool return_sdf,
136
+ bool return_prj_pts,
137
+ int sign_mode)
138
+ {
139
+ if (sign_mode != 0 && sign_mode != 1)
140
+ {
141
+ throw std::runtime_error("sign_mode must be 0 (ray casting) or 1 (fast winding number)");
142
+ }
143
+
144
+ if (sign_mode == 1 && !this->has_winding_data)
145
+ {
146
+ this->build_winding_data(vertices, triangles);
147
+ }
148
+
149
+ int num_queries = query_points.size(0);
150
+ int num_objects = this->object_ids.size(0);
151
+
152
+ auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(query_points.device());
153
+ auto options_f32 = torch::TensorOptions().dtype(torch::kFloat32).device(query_points.device());
154
+
155
+ torch::Tensor out_query_ids = torch::empty({num_queries}, options_i64);
156
+ torch::Tensor out_object_ids = torch::empty({num_queries}, options_i64);
157
+ torch::Tensor out_projected_pts;
158
+ if (return_prj_pts)
159
+ {
160
+ out_projected_pts = torch::empty({num_queries, 3}, options_f32);
161
+ }
162
+ else
163
+ {
164
+ out_projected_pts = torch::empty({0, 3}, options_f32);
165
+ }
166
+ torch::Tensor out_distances = torch::empty({num_queries}, options_f32);
167
+
168
+ mesh_bvh::query_point_mesh_bvh(
169
+ num_queries,
170
+ num_objects,
171
+ (const float3 *)query_points.data_ptr<float>(),
172
+ (const float3 *)vertices.data_ptr<float>(),
173
+ (const int3 *)triangles.data_ptr<int>(),
174
+ (const float3 *)this->aabb_mins.data_ptr<float>(),
175
+ (const float3 *)this->aabb_maxs.data_ptr<float>(),
176
+ (const int2 *)this->bvh_children.data_ptr<int>(),
177
+ this->object_ids.data_ptr<int>(),
178
+ this->has_winding_data ? (const WindingData *)this->winding_data.data_ptr() : nullptr,
179
+ out_query_ids.data_ptr<int64_t>(),
180
+ out_object_ids.data_ptr<int64_t>(),
181
+ return_prj_pts ? (float3 *)out_projected_pts.data_ptr<float>() : nullptr,
182
+ out_distances.data_ptr<float>(),
183
+ return_sdf,
184
+ return_prj_pts,
185
+ sign_mode);
186
+
187
+ return std::make_tuple(out_query_ids, out_object_ids, out_projected_pts, out_distances);
188
+ }
189
+
190
+ void MeshBVH::build_winding_data(
191
+ const torch::Tensor &vertices,
192
+ const torch::Tensor &triangles)
193
+ {
194
+ int num_objects = this->object_ids.size(0);
195
+ int num_nodes = num_objects * 2 - 1;
196
+
197
+ auto options = torch::TensorOptions().dtype(torch::kByte).device(vertices.device());
198
+ this->winding_data = torch::empty({num_nodes * (int64_t)sizeof(WindingData)}, options);
199
+
200
+ mesh_bvh::bottom_up_winding_data(
201
+ num_objects,
202
+ this->object_ids.data_ptr<int>(),
203
+ (const float3 *)vertices.data_ptr<float>(),
204
+ (const int3 *)triangles.data_ptr<int>(),
205
+ this->bvh_parents.data_ptr<int>(),
206
+ (const int2 *)this->bvh_children.data_ptr<int>(),
207
+ (WindingData *)this->winding_data.data_ptr());
208
+
209
+ cudaDeviceSynchronize();
210
+ this->has_winding_data = true;
211
+ }
212
+
213
+ void bind_ds_mesh_bvh(py::module_ &m)
214
+ {
215
+ py::class_<MeshBVH, BVH>(m, "MeshBVH")
216
+ .def(py::init<const torch::Tensor &, const torch::Tensor &>(),
217
+ py::arg("in_aabb_mins"),
218
+ py::arg("in_aabb_maxs"),
219
+ "Construct MeshBVH from Triangle AABBs.")
220
+ .def("get_self_intersection", &MeshBVH::get_self_intersection,
221
+ py::arg("vertices"),
222
+ py::arg("triangles"),
223
+ "Find all self-intersecting triangle pairs")
224
+ .def("is_self_intersection", &MeshBVH::is_self_intersection,
225
+ py::arg("vertices"),
226
+ py::arg("triangles"),
227
+ "Check if there are any self-intersecting triangle pairs")
228
+ .def("get_ray_intersection", [](MeshBVH &self, const torch::Tensor &ray_origins, const torch::Tensor &ray_dirs, const torch::Tensor &vertices, const torch::Tensor &triangles, bool return_distance) -> py::object
229
+ {
230
+ auto result = self.get_ray_intersection(ray_origins, ray_dirs, vertices, triangles, return_distance);
231
+ if (return_distance) {
232
+ return py::cast(result);
233
+ } else {
234
+ return py::cast(std::make_tuple(std::get<0>(result), std::get<1>(result), std::get<2>(result)));
235
+ } }, py::arg("ray_origins"), py::arg("ray_dirs"), py::arg("vertices"), py::arg("triangles"), py::arg("return_distance") = false, "Find all ray-triangle intersections. Returns (ray_ids, triangle_ids, intersect_points, [distances])")
236
+ .def("query_point", &MeshBVH::query_point, py::arg("query_points"), py::arg("vertices"), py::arg("triangles"), py::arg("return_sdf") = false, py::arg("return_prj_pts") = true, py::arg("sign_mode") = 0, "Find the closest triangle to each query point. Returns (query_ids, triangle_ids, projected_points, distances)")
237
+ .def("build_winding_data", &MeshBVH::build_winding_data, py::arg("vertices"), py::arg("triangles"), "Computes the hierarchical area-weighted constants for Fast Winding Number queries.");
238
+ }
@@ -50,11 +50,33 @@ void TriangleMesh::compute_triangle_normals()
50
50
  this->triangle_normals = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
51
51
  triangle_mesh::compute_triangle_normals(
52
52
  this->num_triangles,
53
- reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
54
- reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
53
+ reinterpret_cast<const float3 *>(this->vertices.data_ptr<float>()),
54
+ reinterpret_cast<const int3 *>(this->triangles.data_ptr<int>()),
55
55
  reinterpret_cast<float3 *>(this->triangle_normals.data_ptr<float>()));
56
56
  }
57
57
 
58
+ void TriangleMesh::compute_vertex_normals()
59
+ {
60
+ uint32_t num_vertices = this->vertices.size(0);
61
+ this->vertex_normals = torch::zeros({static_cast<int64_t>(num_vertices), 3}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
62
+
63
+ triangle_mesh::compute_vertex_normals(
64
+ num_vertices,
65
+ this->num_triangles,
66
+ reinterpret_cast<const int3 *>(this->triangles.data_ptr<int>()),
67
+ reinterpret_cast<const float3 *>(this->get_triangle_normals().data_ptr<float>()),
68
+ reinterpret_cast<float3 *>(this->vertex_normals.data_ptr<float>()));
69
+ }
70
+
71
+ torch::Tensor TriangleMesh::get_vertex_normals()
72
+ {
73
+ if (!this->vertex_normals.defined())
74
+ {
75
+ this->compute_vertex_normals();
76
+ }
77
+ return this->vertex_normals;
78
+ }
79
+
58
80
  void TriangleMesh::compute_triangle_areas()
59
81
  {
60
82
  this->triangle_areas = torch::empty({static_cast<int64_t>(this->num_triangles)}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
@@ -97,6 +119,21 @@ bool TriangleMesh::is_self_intersection()
97
119
  return this->bvh.value().is_self_intersection(this->vertices, this->triangles);
98
120
  }
99
121
 
122
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::query_points(
123
+ const torch::Tensor &query_pts,
124
+ bool return_sdf,
125
+ bool return_prj_pts,
126
+ int sign_mode,
127
+ int distance_mode)
128
+ {
129
+ if (distance_mode == 0) {
130
+ this->build_bvh();
131
+ return this->bvh.value().query_point(query_pts, this->vertices, this->triangles, return_sdf, return_prj_pts, sign_mode);
132
+ } else {
133
+ throw std::runtime_error("distance_mode != 0 not implemented yet");
134
+ }
135
+ }
136
+
100
137
  std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::get_ray_intersection(
101
138
  const torch::Tensor &ray_origins,
102
139
  const torch::Tensor &ray_dirs,
@@ -106,6 +143,69 @@ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleM
106
143
  return this->bvh.value().get_ray_intersection(ray_origins, ray_dirs, this->vertices, this->triangles, return_distance);
107
144
  }
108
145
 
146
+ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>, std::optional<torch::Tensor>> TriangleMesh::sample_points(
147
+ int num_points, bool uniform, bool return_normals, bool return_colors, bool use_triangle_normal)
148
+ {
149
+ if (this->num_triangles == 0) {
150
+ throw std::runtime_error("Cannot sample points from an empty mesh.");
151
+ }
152
+
153
+ torch::Tensor tri_indices;
154
+ if (!uniform) {
155
+ tri_indices = torch::randint(0, this->num_triangles, {num_points}, torch::TensorOptions().device(this->vertices.device()).dtype(torch::kInt64));
156
+ } else {
157
+ torch::Tensor areas = this->get_triangle_areas();
158
+ tri_indices = torch::multinomial(areas, num_points, true);
159
+ }
160
+
161
+ torch::Tensor r1_r2 = torch::rand({num_points, 2}, torch::TensorOptions().device(this->vertices.device()).dtype(torch::kFloat32));
162
+ torch::Tensor out_points = torch::empty({num_points, 3}, torch::TensorOptions().device(this->vertices.device()).dtype(torch::kFloat32));
163
+
164
+ torch::Tensor out_normals, out_colors;
165
+ const float3* d_vertex_normals = nullptr;
166
+ const float3* d_triangle_normals = nullptr;
167
+ const float3* d_vertex_colors = nullptr;
168
+ float3* d_out_normals = nullptr;
169
+ float3* d_out_colors = nullptr;
170
+
171
+ if (return_normals) {
172
+ out_normals = torch::empty({num_points, 3}, torch::TensorOptions().device(this->vertices.device()).dtype(torch::kFloat32));
173
+ d_out_normals = reinterpret_cast<float3 *>(out_normals.data_ptr<float>());
174
+ if (use_triangle_normal) {
175
+ d_triangle_normals = reinterpret_cast<const float3 *>(this->get_triangle_normals().data_ptr<float>());
176
+ } else {
177
+ d_vertex_normals = reinterpret_cast<const float3 *>(this->get_vertex_normals().data_ptr<float>());
178
+ }
179
+ }
180
+
181
+ if (return_colors) {
182
+ if (!this->vertex_colors.defined()) {
183
+ throw std::runtime_error("Cannot sample colors because vertex_colors is not defined.");
184
+ }
185
+ out_colors = torch::empty({num_points, 3}, torch::TensorOptions().device(this->vertices.device()).dtype(torch::kFloat32));
186
+ d_out_colors = reinterpret_cast<float3 *>(out_colors.data_ptr<float>());
187
+ d_vertex_colors = reinterpret_cast<const float3 *>(this->vertex_colors.data_ptr<float>());
188
+ }
189
+
190
+ triangle_mesh::sample_points_triangle_mesh(
191
+ num_points,
192
+ reinterpret_cast<const float3 *>(this->vertices.data_ptr<float>()),
193
+ reinterpret_cast<const int3 *>(this->triangles.data_ptr<int>()),
194
+ reinterpret_cast<const int64_t *>(tri_indices.data_ptr<int64_t>()),
195
+ reinterpret_cast<const float2 *>(r1_r2.data_ptr<float>()),
196
+ d_vertex_normals,
197
+ d_triangle_normals,
198
+ d_vertex_colors,
199
+ reinterpret_cast<float3 *>(out_points.data_ptr<float>()),
200
+ d_out_normals,
201
+ d_out_colors);
202
+
203
+ std::optional<torch::Tensor> opt_normals = return_normals ? std::make_optional(out_normals) : std::nullopt;
204
+ std::optional<torch::Tensor> opt_colors = return_colors ? std::make_optional(out_colors) : std::nullopt;
205
+
206
+ return std::make_tuple(out_points, tri_indices, opt_normals, opt_colors);
207
+ }
208
+
109
209
  torch::Tensor TriangleMesh::get_triangle_areas()
110
210
  {
111
211
  if (!this->triangle_areas.defined())
@@ -338,6 +438,13 @@ void bind_ds_triangle_mesh(py::module_ &m)
338
438
  py::arg("ray_dirs"),
339
439
  py::arg("return_distance") = false,
340
440
  "Find all ray-triangle intersections. Returns (ray_ids, triangle_ids, intersect_points, [distances])")
441
+ .def("query_points", &TriangleMesh::query_points,
442
+ py::arg("query_pts"),
443
+ py::arg("return_sdf") = false,
444
+ py::arg("return_prj_pts") = true,
445
+ py::arg("sign_mode") = 0,
446
+ py::arg("distance_mode") = 0,
447
+ "Query points against the mesh. Returns (query_ids, triangle_ids, projected_points, distances)")
341
448
  .def_property_readonly("edges", &TriangleMesh::get_edges)
342
449
  .def_property_readonly("edge_to_triangle_offsets", &TriangleMesh::get_edge_to_triangle_offsets)
343
450
  .def_property_readonly("edge_to_triangle_counts", &TriangleMesh::get_edge_to_triangle_counts)
@@ -356,6 +463,8 @@ void bind_ds_triangle_mesh(py::module_ &m)
356
463
  .def("is_manifold", &TriangleMesh::is_manifold, py::arg("allow_boundary_edge") = true)
357
464
  .def("get_non_manifold_vertices", &TriangleMesh::get_non_manifold_vertices)
358
465
  .def("remove_triangles_by_mask", &TriangleMesh::remove_triangles_by_mask, py::arg("keep_mask"))
466
+ .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, "Sample points on the mesh")
467
+ .def("compute_vertex_normals", &TriangleMesh::compute_vertex_normals)
359
468
  .def_property_readonly("euler_characteristic", &TriangleMesh::get_euler_characteristic)
360
469
  .def_property_readonly("genus", &TriangleMesh::get_genus);
361
470
  }