conquer3d 0.2.1__tar.gz → 0.2.4__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 (68) hide show
  1. {conquer3d-0.2.1/conquer3d.egg-info → conquer3d-0.2.4}/PKG-INFO +6 -4
  2. {conquer3d-0.2.1 → conquer3d-0.2.4}/README.md +4 -2
  3. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/_C.pyi +107 -1
  4. conquer3d-0.2.4/conquer3d/_C.so +0 -0
  5. conquer3d-0.2.4/conquer3d/__init__.py +8 -0
  6. conquer3d-0.2.4/conquer3d/creation/__init__.py +5 -0
  7. conquer3d-0.2.4/conquer3d/creation/triangle_creation.py +23 -0
  8. conquer3d-0.2.4/conquer3d/csrc/binds/creation/triangle_creation.cpp +13 -0
  9. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/bvh.cpp +38 -2
  10. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +1 -1
  11. conquer3d-0.2.4/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +22 -0
  12. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +1 -1
  13. conquer3d-0.2.4/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +338 -0
  14. conquer3d-0.2.4/conquer3d/csrc/creation/triangle_creation.h +97 -0
  15. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/bvh.cu +84 -0
  16. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/bvh.h +17 -4
  17. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/kdtree.h +40 -33
  18. conquer3d-0.2.4/conquer3d/csrc/data_structure/mesh_bvh.cu +98 -0
  19. conquer3d-0.2.4/conquer3d/csrc/data_structure/mesh_bvh.h +24 -0
  20. conquer3d-0.2.4/conquer3d/csrc/data_structure/triangle_mesh.cu +397 -0
  21. conquer3d-0.2.4/conquer3d/csrc/data_structure/triangle_mesh.h +134 -0
  22. conquer3d-0.2.4/conquer3d/csrc/primitive/edge.h +31 -0
  23. conquer3d-0.2.4/conquer3d/csrc/primitive/triangles.h +106 -0
  24. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/pybind.cpp +7 -0
  25. conquer3d-0.2.4/conquer3d/data_structure/__init__.py +13 -0
  26. {conquer3d-0.2.1 → conquer3d-0.2.4/conquer3d.egg-info}/PKG-INFO +6 -4
  27. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/SOURCES.txt +12 -0
  28. {conquer3d-0.2.1 → conquer3d-0.2.4}/pyproject.toml +2 -2
  29. {conquer3d-0.2.1 → conquer3d-0.2.4}/setup.py +1 -1
  30. conquer3d-0.2.1/conquer3d/_C.so +0 -0
  31. conquer3d-0.2.1/conquer3d/__init__.py +0 -5
  32. conquer3d-0.2.1/conquer3d/data_structure/__init__.py +0 -8
  33. {conquer3d-0.2.1 → conquer3d-0.2.4}/LICENSE +0 -0
  34. {conquer3d-0.2.1 → conquer3d-0.2.4}/MANIFEST.in +0 -0
  35. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
  36. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
  37. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
  38. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/check.h +0 -0
  39. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/constants.h +0 -0
  40. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
  41. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
  42. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
  43. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/data_structure/zcurve.h +0 -0
  44. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f2x2.h +0 -0
  45. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f3x1.h +0 -0
  46. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f3x3.h +0 -0
  47. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f3x4.h +0 -0
  48. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f4x1.h +0 -0
  49. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/f4x4.h +0 -0
  50. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/maths.h +0 -0
  51. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/maths/ops.h +0 -0
  52. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/aabb.h +0 -0
  53. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs.cu +0 -0
  54. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs.h +0 -0
  55. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
  56. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
  57. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs.cu +0 -0
  58. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs.h +0 -0
  59. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
  60. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
  61. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/primitive/__init__.py +0 -0
  62. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/primitive/gs.py +0 -0
  63. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d/primitive/pgs.py +0 -0
  64. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/dependency_links.txt +0 -0
  65. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/not-zip-safe +0 -0
  66. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/requires.txt +0 -0
  67. {conquer3d-0.2.1 → conquer3d-0.2.4}/conquer3d.egg-info/top_level.txt +0 -0
  68. {conquer3d-0.2.1 → conquer3d-0.2.4}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conquer3d
3
- Version: 0.2.1
3
+ Version: 0.2.4
4
4
  Summary: Geometric Cuda Tool Box
5
5
  Author-email: Do Hoang Khoi <khoido8899@gmail.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/KhoiDOO/conquer3d
8
8
  Project-URL: Repository, https://github.com/KhoiDOO/conquer3d.git
9
9
  Project-URL: Documentation, https://github.com/KhoiDOO/conquer3d#readme
@@ -90,5 +90,7 @@ series = {EGGH-HPG'12}
90
90
  ## Repository
91
91
  - [cuBQL](https://github.com/NVIDIA/cuBQL)
92
92
  - [cudaKDTree](https://github.com/ingowald/cudaKDTree)
93
- - [Kaolin] (https://github.com/NVIDIAGameWorks/kaolin)
94
- - [Pytorch3D] (https://github.com/facebookresearch/pytorch3d)
93
+ - [Kaolin](https://github.com/NVIDIAGameWorks/kaolin)
94
+ - [Pytorch3D](https://github.com/facebookresearch/pytorch3d)
95
+ - [Open3D](https://www.open3d.org/)
96
+ - [trimesh](https://github.com/mikedh/trimesh)
@@ -66,5 +66,7 @@ series = {EGGH-HPG'12}
66
66
  ## Repository
67
67
  - [cuBQL](https://github.com/NVIDIA/cuBQL)
68
68
  - [cudaKDTree](https://github.com/ingowald/cudaKDTree)
69
- - [Kaolin] (https://github.com/NVIDIAGameWorks/kaolin)
70
- - [Pytorch3D] (https://github.com/facebookresearch/pytorch3d)
69
+ - [Kaolin](https://github.com/NVIDIAGameWorks/kaolin)
70
+ - [Pytorch3D](https://github.com/facebookresearch/pytorch3d)
71
+ - [Open3D](https://www.open3d.org/)
72
+ - [trimesh](https://github.com/mikedh/trimesh)
@@ -4,7 +4,7 @@ Geocutool Python bindings
4
4
  from __future__ import annotations
5
5
  import torch
6
6
  import typing
7
- __all__: list[str] = ['BVH', 'GSBVH', 'KDTree', 'PGSBVH', 'compute_gs_aabb_func', 'compute_gs_covi_func', 'solve_gs_neighbor_mahalanobis_radius_func', 'solve_pgs_cluster_tangency_radius_func']
7
+ __all__: list[str] = ['BVH', 'GSBVH', 'KDTree', 'MeshBVH', 'PGSBVH', 'TriangleMesh', 'compute_gs_aabb_func', 'compute_gs_covi_func', 'create_sphere', 'solve_gs_neighbor_mahalanobis_radius_func', 'solve_pgs_cluster_tangency_radius_func']
8
8
  class BVH:
9
9
  def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
10
10
  """
@@ -14,6 +14,10 @@ class BVH:
14
14
  """
15
15
  Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).
16
16
  """
17
+ def query_self(self) -> tuple[torch.Tensor, torch.Tensor]:
18
+ """
19
+ Query the BVH against itself. Returns unique overlapping (query_ids, object_ids).
20
+ """
17
21
  class GSBVH(BVH):
18
22
  def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
19
23
  """
@@ -40,6 +44,19 @@ class KDTree:
40
44
  """
41
45
  Queries the KDTree for the K nearest neighbors.
42
46
  """
47
+ class MeshBVH(BVH):
48
+ def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
49
+ """
50
+ Construct MeshBVH from Triangle AABBs.
51
+ """
52
+ def get_self_intersection(self, vertices: torch.Tensor, triangles: torch.Tensor) -> torch.Tensor:
53
+ """
54
+ Find all self-intersecting triangle pairs
55
+ """
56
+ def is_self_intersection(self, vertices: torch.Tensor, triangles: torch.Tensor) -> bool:
57
+ """
58
+ Check if there are any self-intersecting triangle pairs
59
+ """
43
60
  class PGSBVH(BVH):
44
61
  def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
45
62
  """
@@ -53,6 +70,91 @@ class PGSBVH(BVH):
53
70
  """
54
71
  Perform exact Broad-to-Narrow phase intersection between Voxels and PGS.
55
72
  """
73
+ class TriangleMesh:
74
+ def __init__(self, in_vertices: torch.Tensor, in_triangles: torch.Tensor, in_vertex_normals: torch.Tensor | None = None, in_vertex_colors: torch.Tensor | None = None) -> None:
75
+ ...
76
+ def build_bvh(self) -> MeshBVH:
77
+ ...
78
+ def compute_edges_to_triangle_map(self) -> None:
79
+ ...
80
+ def compute_triangle_areas(self) -> None:
81
+ ...
82
+ def compute_triangle_normals(self) -> None:
83
+ ...
84
+ def compute_vertices_to_triangle_map(self) -> None:
85
+ ...
86
+ def get_edges_to_triangle_map(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
87
+ ...
88
+ def get_non_manifold_vertices(self) -> torch.Tensor:
89
+ ...
90
+ def get_self_intersection(self) -> torch.Tensor:
91
+ ...
92
+ def get_vertices_to_triangle_map(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
93
+ ...
94
+ def is_edge_manifold(self, allow_boundary_edge: bool = True) -> bool:
95
+ ...
96
+ def is_manifold(self, allow_boundary_edge: bool = True) -> bool:
97
+ ...
98
+ def is_self_intersection(self) -> bool:
99
+ ...
100
+ def is_vertex_manifold(self) -> bool:
101
+ ...
102
+ def remove_triangles_by_mask(self, keep_mask: torch.Tensor) -> None:
103
+ ...
104
+ @property
105
+ def bvh(self) -> MeshBVH:
106
+ ...
107
+ @property
108
+ def edge_to_triangle_counts(self) -> torch.Tensor:
109
+ ...
110
+ @property
111
+ def edge_to_triangle_indices(self) -> torch.Tensor:
112
+ ...
113
+ @property
114
+ def edge_to_triangle_offsets(self) -> torch.Tensor:
115
+ ...
116
+ @property
117
+ def edges(self) -> torch.Tensor:
118
+ ...
119
+ @property
120
+ def euler_characteristic(self) -> int:
121
+ ...
122
+ @property
123
+ def genus(self) -> int:
124
+ ...
125
+ @property
126
+ def num_triangles(self) -> int:
127
+ ...
128
+ @property
129
+ def surface_area(self) -> torch.Tensor:
130
+ ...
131
+ @property
132
+ def triangle_areas(self) -> torch.Tensor:
133
+ ...
134
+ @property
135
+ def triangle_normals(self) -> torch.Tensor:
136
+ ...
137
+ @property
138
+ def triangles(self) -> torch.Tensor:
139
+ ...
140
+ @property
141
+ def vertex_colors(self) -> torch.Tensor:
142
+ ...
143
+ @property
144
+ def vertex_normals(self) -> torch.Tensor:
145
+ ...
146
+ @property
147
+ def vertex_to_triangle_counts(self) -> torch.Tensor:
148
+ ...
149
+ @property
150
+ def vertex_to_triangle_indices(self) -> torch.Tensor:
151
+ ...
152
+ @property
153
+ def vertex_to_triangle_offsets(self) -> torch.Tensor:
154
+ ...
155
+ @property
156
+ def vertices(self) -> torch.Tensor:
157
+ ...
56
158
  def compute_gs_aabb_func(means: torch.Tensor, scales: torch.Tensor, covis: torch.Tensor, isos: torch.Tensor | None, iso: float, tol: float, level: int) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
57
159
  """
58
160
  Compute AABB for 3D Gaussians
@@ -61,6 +163,10 @@ def compute_gs_covi_func(means: torch.Tensor, rotations: torch.Tensor, scales: t
61
163
  """
62
164
  Compute covariance matrices for 3D Gaussians
63
165
  """
166
+ def create_sphere(sectors: int = 32, stacks: int = 16, radius: float = 1.0) -> tuple[torch.Tensor, torch.Tensor]:
167
+ """
168
+ Creates a UV sphere returning (vertices, triangles) tensors on the CPU.
169
+ """
64
170
  def solve_gs_neighbor_mahalanobis_radius_func(means: torch.Tensor, covis: torch.Tensor, k: int) -> torch.Tensor:
65
171
  """
66
172
  Solve Gaussian Neighbor Mahalanobis Radius
Binary file
@@ -0,0 +1,8 @@
1
+ import torch
2
+
3
+ from . import _C
4
+ from . import creation
5
+ from . import data_structure
6
+ from . import primitive
7
+
8
+ __all__ = ['_C', 'creation', 'data_structure', 'primitive']
@@ -0,0 +1,5 @@
1
+ from .triangle_creation import create_sphere
2
+
3
+ __all__ = [
4
+ "create_sphere",
5
+ ]
@@ -0,0 +1,23 @@
1
+ import torch
2
+ from typing import Tuple
3
+
4
+ from .._C import create_sphere as create_sphere_c
5
+
6
+ def create_sphere(
7
+ sectors: int = 32,
8
+ stacks: int = 16,
9
+ radius: float = 1.0
10
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
11
+ """
12
+ Creates a UV sphere returning (vertices, triangles) tensors on the CPU.
13
+
14
+ Args:
15
+ sectors (int, optional): Number of longitudinal sectors. Defaults to 32.
16
+ stacks (int, optional): Number of latitudinal stacks. Defaults to 16.
17
+ radius (float, optional): Radius of the sphere. Defaults to 1.0.
18
+
19
+ Returns:
20
+ vertices: (N, 3) float32 tensor of vertex positions.
21
+ triangles: (M, 3) int32 tensor of triangle indices.
22
+ """
23
+ return create_sphere_c(sectors, stacks, radius)
@@ -0,0 +1,13 @@
1
+ #include <torch/extension.h>
2
+ #include <pybind11/pybind11.h>
3
+ #include "../../creation/triangle_creation.h"
4
+
5
+ namespace py = pybind11;
6
+
7
+ void bind_creation_triangle_creation(py::module_& m) {
8
+ m.def("create_sphere", &triangle_creation::create_sphere,
9
+ py::arg("sectors") = 32,
10
+ py::arg("stacks") = 16,
11
+ py::arg("radius") = 1.0f,
12
+ "Creates a UV sphere returning (vertices, triangles) tensors on the CPU.");
13
+ }
@@ -120,7 +120,40 @@ std::tuple<torch::Tensor, torch::Tensor> BVH::query(
120
120
  );
121
121
  }
122
122
 
123
- void bind_ds_bvh(py::module &m)
123
+ std::tuple<torch::Tensor, torch::Tensor> BVH::query_self()
124
+ {
125
+ auto options_int64 = this->aabb_mins.options().dtype(torch::kInt64);
126
+
127
+ if (this->num_objects == 0) {
128
+ throw std::runtime_error("Cannot query BVH with 0 objects.");
129
+ }
130
+
131
+ torch::Tensor out_query_ids = torch::empty({BVH_MAX_CAPACITY}, options_int64);
132
+ torch::Tensor out_object_ids = torch::empty({BVH_MAX_CAPACITY}, options_int64);
133
+ torch::Tensor hit_counter = torch::zeros({1}, options_int64);
134
+
135
+ bvh::query_self(
136
+ this->num_objects,
137
+ reinterpret_cast<const float3 *>(this->aabb_mins.data_ptr<float>()),
138
+ reinterpret_cast<const float3 *>(this->aabb_maxs.data_ptr<float>()),
139
+ reinterpret_cast<const int2 *>(this->bvh_children.data_ptr<int>()),
140
+ reinterpret_cast<const int *>(this->object_ids.data_ptr<int>()),
141
+ reinterpret_cast<int64_t *>(out_query_ids.data_ptr<int64_t>()),
142
+ reinterpret_cast<int64_t *>(out_object_ids.data_ptr<int64_t>()),
143
+ reinterpret_cast<int64_t *>(hit_counter.data_ptr<int64_t>()),
144
+ static_cast<int64_t>(BVH_MAX_CAPACITY)
145
+ );
146
+
147
+ int64_t num_hits = hit_counter.item<int64_t>();
148
+ num_hits = std::min(num_hits, static_cast<int64_t>(BVH_MAX_CAPACITY));
149
+
150
+ return std::make_tuple(
151
+ out_query_ids.slice(0, 0, num_hits),
152
+ out_object_ids.slice(0, 0, num_hits)
153
+ );
154
+ }
155
+
156
+ void bind_ds_bvh(py::module_& m)
124
157
  {
125
158
  py::class_<BVH>(m, "BVH")
126
159
  .def(py::init<const torch::Tensor &, const torch::Tensor &>(),
@@ -131,5 +164,8 @@ void bind_ds_bvh(py::module &m)
131
164
  .def("query", &BVH::query,
132
165
  py::arg("query_aabb_mins"),
133
166
  py::arg("query_aabb_maxs"),
134
- "Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).");
167
+ "Query the BVH with bounding boxes or segments. Returns (query_ids, object_ids).")
168
+
169
+ .def("query_self", &BVH::query_self,
170
+ "Query the BVH against itself. Returns unique overlapping (query_ids, object_ids).");
135
171
  }
@@ -312,7 +312,7 @@ std::tuple<torch::Tensor, torch::Tensor> GSBVH::query_edge(
312
312
  return std::make_tuple(hit_mask, out_gaus_ids);
313
313
  }
314
314
 
315
- void bind_ds_gs_bvh(py::module &m)
315
+ void bind_ds_gs_bvh(py::module_& m)
316
316
  {
317
317
  py::class_<GSBVH, BVH>(m, "GSBVH")
318
318
  .def(py::init<const torch::Tensor &, const torch::Tensor &>(),
@@ -0,0 +1,22 @@
1
+ #include <torch/extension.h>
2
+ #include "../../data_structure/mesh_bvh.h"
3
+ #include <pybind11/pybind11.h>
4
+
5
+ namespace py = pybind11;
6
+
7
+ void bind_ds_mesh_bvh(py::module_& m)
8
+ {
9
+ py::class_<MeshBVH, BVH>(m, "MeshBVH")
10
+ .def(py::init<const torch::Tensor &, const torch::Tensor &>(),
11
+ py::arg("in_aabb_mins"),
12
+ py::arg("in_aabb_maxs"),
13
+ "Construct MeshBVH from Triangle AABBs.")
14
+ .def("get_self_intersection", &MeshBVH::get_self_intersection,
15
+ py::arg("vertices"),
16
+ py::arg("triangles"),
17
+ "Find all self-intersecting triangle pairs")
18
+ .def("is_self_intersection", &MeshBVH::is_self_intersection,
19
+ py::arg("vertices"),
20
+ py::arg("triangles"),
21
+ "Check if there are any self-intersecting triangle pairs");
22
+ }
@@ -222,7 +222,7 @@ std::tuple<torch::Tensor, torch::Tensor> PGSBVH::query_edge(
222
222
  return std::make_tuple(hit_mask, out_gaus_ids);
223
223
  }
224
224
 
225
- void bind_ds_pgs_bvh(py::module &m)
225
+ void bind_ds_pgs_bvh(py::module_& m)
226
226
  {
227
227
  py::class_<PGSBVH, BVH>(m, "PGSBVH")
228
228
  .def(py::init<const torch::Tensor &, const torch::Tensor &>(),
@@ -0,0 +1,338 @@
1
+ #include <torch/extension.h>
2
+ #include "../../data_structure/triangle_mesh.h"
3
+ #include "../../check.h"
4
+
5
+ #include <pybind11/pybind11.h>
6
+ #include <optional>
7
+ #include <vector>
8
+
9
+ namespace py = pybind11;
10
+
11
+ #include <optional>
12
+
13
+ TriangleMesh::TriangleMesh(
14
+ const torch::Tensor &in_vertices,
15
+ const torch::Tensor &in_triangles,
16
+ std::optional<torch::Tensor> in_vertex_normals,
17
+ std::optional<torch::Tensor> in_vertex_colors)
18
+ {
19
+ CHECK_INPUT(in_vertices);
20
+ CHECK_INPUT(in_triangles);
21
+ TORCH_CHECK(in_vertices.scalar_type() == torch::kFloat32, "vertices must be float32");
22
+ TORCH_CHECK(in_triangles.scalar_type() == torch::kInt32, "triangles must be int32");
23
+ TORCH_CHECK(in_vertices.size(1) == 3, "vertices must have shape (N, 3)");
24
+ TORCH_CHECK(in_triangles.size(1) == 3, "triangles must have shape (M, 3)");
25
+
26
+ this->num_triangles = static_cast<uint32_t>(in_triangles.size(0));
27
+
28
+ this->vertices = in_vertices;
29
+ this->triangles = in_triangles;
30
+
31
+ if (in_vertex_normals.has_value() && in_vertex_normals->defined())
32
+ {
33
+ CHECK_INPUT(*in_vertex_normals);
34
+ TORCH_CHECK(in_vertex_normals->scalar_type() == torch::kFloat32, "vertex_normals must be float32");
35
+ TORCH_CHECK(in_vertex_normals->size(1) == 3, "vertex_normals must have shape (N, 3)");
36
+ this->vertex_normals = in_vertex_normals->clone();
37
+ }
38
+
39
+ if (in_vertex_colors.has_value() && in_vertex_colors->defined())
40
+ {
41
+ CHECK_INPUT(*in_vertex_colors);
42
+ TORCH_CHECK(in_vertex_colors->scalar_type() == torch::kFloat32, "vertex_colors must be float32");
43
+ TORCH_CHECK(in_vertex_colors->size(1) == 3, "vertex_colors must have shape (N, 3)");
44
+ this->vertex_colors = in_vertex_colors->clone();
45
+ }
46
+ }
47
+
48
+ void TriangleMesh::compute_triangle_normals()
49
+ {
50
+ this->triangle_normals = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
51
+ triangle_mesh::compute_triangle_normals(
52
+ this->num_triangles,
53
+ reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
54
+ reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
55
+ reinterpret_cast<float3 *>(this->triangle_normals.data_ptr<float>()));
56
+ }
57
+
58
+ void TriangleMesh::compute_triangle_areas()
59
+ {
60
+ this->triangle_areas = torch::empty({static_cast<int64_t>(this->num_triangles)}, torch::dtype(torch::kFloat32).device(this->vertices.device()));
61
+ triangle_mesh::compute_triangle_areas(
62
+ this->num_triangles,
63
+ reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
64
+ reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
65
+ reinterpret_cast<float *>(this->triangle_areas.data_ptr<float>()));
66
+ }
67
+
68
+ MeshBVH TriangleMesh::build_bvh()
69
+ {
70
+ if (!this->bvh.has_value())
71
+ {
72
+ auto options = torch::TensorOptions().dtype(torch::kFloat32).device(this->vertices.device());
73
+ torch::Tensor aabb_mins = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, options);
74
+ torch::Tensor aabb_maxs = torch::empty({static_cast<int64_t>(this->num_triangles), 3}, options);
75
+
76
+ triangle_mesh::compute_triangle_aabbs(
77
+ this->num_triangles,
78
+ reinterpret_cast<float3 *>(this->vertices.data_ptr<float>()),
79
+ reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
80
+ reinterpret_cast<float3 *>(aabb_mins.data_ptr<float>()),
81
+ reinterpret_cast<float3 *>(aabb_maxs.data_ptr<float>()));
82
+
83
+ this->bvh = MeshBVH(aabb_mins, aabb_maxs);
84
+ }
85
+ return this->bvh.value();
86
+ }
87
+
88
+ torch::Tensor TriangleMesh::get_self_intersection()
89
+ {
90
+ this->build_bvh();
91
+ return this->bvh.value().get_self_intersection(this->vertices, this->triangles);
92
+ }
93
+
94
+ bool TriangleMesh::is_self_intersection()
95
+ {
96
+ this->build_bvh();
97
+ return this->bvh.value().is_self_intersection(this->vertices, this->triangles);
98
+ }
99
+
100
+ torch::Tensor TriangleMesh::get_triangle_areas()
101
+ {
102
+ if (!this->triangle_areas.defined())
103
+ {
104
+ this->compute_triangle_areas();
105
+ }
106
+ return this->triangle_areas;
107
+ }
108
+
109
+ torch::Tensor TriangleMesh::get_triangle_normals()
110
+ {
111
+ if (!this->triangle_normals.defined())
112
+ {
113
+ this->compute_triangle_normals();
114
+ }
115
+ return this->triangle_normals;
116
+ }
117
+
118
+ torch::Tensor TriangleMesh::get_surface_area()
119
+ {
120
+ if (!this->surface_area.defined())
121
+ {
122
+ this->surface_area = this->get_triangle_areas().sum();
123
+ }
124
+ return this->surface_area;
125
+ }
126
+
127
+ void TriangleMesh::compute_edges_to_triangle_map()
128
+ {
129
+ if (this->num_triangles == 0)
130
+ {
131
+ auto options_i32 = torch::TensorOptions().dtype(torch::kInt32).device(this->triangles.device());
132
+ this->edges = torch::empty({0, 2}, options_i32);
133
+ this->edge_to_triangle_offsets = torch::empty({0}, options_i32);
134
+ this->edge_to_triangle_counts = torch::empty({0}, options_i32);
135
+ this->edge_to_triangle_indices = torch::empty({0}, options_i32);
136
+ return;
137
+ }
138
+
139
+ triangle_mesh::compute_edges_to_triangle_map(
140
+ this->num_triangles,
141
+ reinterpret_cast<int3 *>(this->triangles.data_ptr<int>()),
142
+ this->edges,
143
+ this->edge_to_triangle_offsets,
144
+ this->edge_to_triangle_counts,
145
+ this->edge_to_triangle_indices);
146
+ }
147
+
148
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::get_edges_to_triangle_map()
149
+ {
150
+ if (!this->edges.defined())
151
+ {
152
+ this->compute_edges_to_triangle_map();
153
+ }
154
+ return std::make_tuple(
155
+ this->edges,
156
+ this->edge_to_triangle_offsets,
157
+ this->edge_to_triangle_counts,
158
+ this->edge_to_triangle_indices);
159
+ }
160
+
161
+ torch::Tensor TriangleMesh::get_edges()
162
+ {
163
+ if (!this->edges.defined())
164
+ this->compute_edges_to_triangle_map();
165
+ return this->edges;
166
+ }
167
+ torch::Tensor TriangleMesh::get_edge_to_triangle_offsets()
168
+ {
169
+ if (!this->edge_to_triangle_offsets.defined())
170
+ this->compute_edges_to_triangle_map();
171
+ return this->edge_to_triangle_offsets;
172
+ }
173
+ torch::Tensor TriangleMesh::get_edge_to_triangle_counts()
174
+ {
175
+ if (!this->edge_to_triangle_counts.defined())
176
+ this->compute_edges_to_triangle_map();
177
+ return this->edge_to_triangle_counts;
178
+ }
179
+ torch::Tensor TriangleMesh::get_edge_to_triangle_indices()
180
+ {
181
+ if (!this->edge_to_triangle_indices.defined())
182
+ this->compute_edges_to_triangle_map();
183
+ return this->edge_to_triangle_indices;
184
+ }
185
+
186
+ bool TriangleMesh::is_edge_manifold(bool allow_boundary_edge)
187
+ {
188
+ if (this->num_triangles == 0)
189
+ return true;
190
+ torch::Tensor counts = this->get_edge_to_triangle_counts();
191
+ if (allow_boundary_edge)
192
+ {
193
+ return (counts <= 2).all().item<bool>();
194
+ }
195
+ else
196
+ {
197
+ return (counts == 2).all().item<bool>();
198
+ }
199
+ }
200
+
201
+ void TriangleMesh::remove_triangles_by_mask(const torch::Tensor &keep_mask)
202
+ {
203
+ CHECK_INPUT(keep_mask);
204
+ TORCH_CHECK(keep_mask.scalar_type() == torch::kBool, "keep_mask must be a boolean tensor");
205
+ TORCH_CHECK(keep_mask.dim() == 1 && keep_mask.size(0) == this->num_triangles, "keep_mask must have shape (num_triangles,)");
206
+
207
+ this->triangles = this->triangles.index({keep_mask});
208
+ this->num_triangles = this->triangles.size(0);
209
+
210
+ // Invalidate all caches
211
+ this->triangle_areas = torch::Tensor();
212
+ this->triangle_normals = torch::Tensor();
213
+ this->surface_area = torch::Tensor();
214
+ this->bvh.reset();
215
+ this->edges = torch::Tensor();
216
+ this->edge_to_triangle_offsets = torch::Tensor();
217
+ this->edge_to_triangle_counts = torch::Tensor();
218
+ this->edge_to_triangle_indices = torch::Tensor();
219
+
220
+ this->vertex_to_triangle_offsets = torch::Tensor();
221
+ this->vertex_to_triangle_counts = torch::Tensor();
222
+ this->vertex_to_triangle_indices = torch::Tensor();
223
+ }
224
+
225
+ int32_t TriangleMesh::get_euler_characteristic()
226
+ {
227
+ int32_t V = this->vertices.size(0);
228
+ int32_t E = this->get_edges().size(0);
229
+ int32_t F = this->num_triangles;
230
+ return V - E + F;
231
+ }
232
+
233
+ int32_t TriangleMesh::get_genus()
234
+ {
235
+ // For a single closed connected component, chi = 2 - 2g => g = (2 - chi) / 2
236
+ int32_t chi = this->get_euler_characteristic();
237
+ return (2 - chi) / 2;
238
+ }
239
+
240
+ void TriangleMesh::compute_vertices_to_triangle_map()
241
+ {
242
+ if (this->vertex_to_triangle_offsets.defined())
243
+ return;
244
+
245
+ uint32_t num_vertices = this->vertices.size(0);
246
+ triangle_mesh::build_vertices_to_triangle_map(
247
+ num_vertices,
248
+ this->num_triangles,
249
+ this->triangles,
250
+ this->vertex_to_triangle_counts,
251
+ this->vertex_to_triangle_offsets,
252
+ this->vertex_to_triangle_indices);
253
+ }
254
+
255
+ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::get_vertices_to_triangle_map()
256
+ {
257
+ this->compute_vertices_to_triangle_map();
258
+ return {this->vertex_to_triangle_offsets, this->vertex_to_triangle_counts, this->vertex_to_triangle_indices};
259
+ }
260
+
261
+ torch::Tensor TriangleMesh::get_vertex_to_triangle_offsets()
262
+ {
263
+ this->compute_vertices_to_triangle_map();
264
+ return this->vertex_to_triangle_offsets;
265
+ }
266
+
267
+ torch::Tensor TriangleMesh::get_vertex_to_triangle_counts()
268
+ {
269
+ this->compute_vertices_to_triangle_map();
270
+ return this->vertex_to_triangle_counts;
271
+ }
272
+
273
+ torch::Tensor TriangleMesh::get_vertex_to_triangle_indices() {
274
+ this->compute_vertices_to_triangle_map();
275
+ return this->vertex_to_triangle_indices;
276
+ }
277
+
278
+ torch::Tensor TriangleMesh::get_non_manifold_vertices() {
279
+ this->compute_vertices_to_triangle_map();
280
+ return triangle_mesh::get_non_manifold_vertices(
281
+ this->vertices.size(0),
282
+ this->triangles,
283
+ this->vertex_to_triangle_offsets,
284
+ this->vertex_to_triangle_counts,
285
+ this->vertex_to_triangle_indices
286
+ );
287
+ }
288
+
289
+ bool TriangleMesh::is_vertex_manifold() {
290
+ torch::Tensor nm_vertices = this->get_non_manifold_vertices();
291
+ return nm_vertices.size(0) == 0;
292
+ }
293
+
294
+ bool TriangleMesh::is_manifold(bool allow_boundary_edge) {
295
+ return this->is_edge_manifold(allow_boundary_edge) && this->is_vertex_manifold() && !this->is_self_intersection();
296
+ }
297
+
298
+ void bind_ds_triangle_mesh(py::module_ &m)
299
+ {
300
+ py::class_<TriangleMesh>(m, "TriangleMesh")
301
+ .def(py::init<const torch::Tensor &, const torch::Tensor &, std::optional<torch::Tensor>, std::optional<torch::Tensor>>(),
302
+ py::arg("in_vertices"),
303
+ py::arg("in_triangles"),
304
+ py::arg("in_vertex_normals") = std::nullopt,
305
+ py::arg("in_vertex_colors") = std::nullopt)
306
+ .def_property_readonly("num_triangles", &TriangleMesh::get_num_triangles)
307
+ .def_property_readonly("vertices", &TriangleMesh::get_vertices)
308
+ .def_property_readonly("vertex_normals", &TriangleMesh::get_vertex_normals)
309
+ .def_property_readonly("vertex_colors", &TriangleMesh::get_vertex_colors)
310
+ .def_property_readonly("triangles", &TriangleMesh::get_triangles)
311
+ .def_property_readonly("triangle_areas", &TriangleMesh::get_triangle_areas)
312
+ .def_property_readonly("triangle_normals", &TriangleMesh::get_triangle_normals)
313
+ .def_property_readonly("surface_area", &TriangleMesh::get_surface_area)
314
+ .def_property_readonly("bvh", &TriangleMesh::build_bvh)
315
+ .def("build_bvh", &TriangleMesh::build_bvh)
316
+ .def("get_self_intersection", &TriangleMesh::get_self_intersection)
317
+ .def("is_self_intersection", &TriangleMesh::is_self_intersection)
318
+ .def_property_readonly("edges", &TriangleMesh::get_edges)
319
+ .def_property_readonly("edge_to_triangle_offsets", &TriangleMesh::get_edge_to_triangle_offsets)
320
+ .def_property_readonly("edge_to_triangle_counts", &TriangleMesh::get_edge_to_triangle_counts)
321
+ .def_property_readonly("edge_to_triangle_indices", &TriangleMesh::get_edge_to_triangle_indices)
322
+ .def("compute_triangle_areas", &TriangleMesh::compute_triangle_areas)
323
+ .def("compute_triangle_normals", &TriangleMesh::compute_triangle_normals)
324
+ .def("compute_edges_to_triangle_map", &TriangleMesh::compute_edges_to_triangle_map)
325
+ .def("get_edges_to_triangle_map", &TriangleMesh::get_edges_to_triangle_map)
326
+ .def("compute_vertices_to_triangle_map", &TriangleMesh::compute_vertices_to_triangle_map)
327
+ .def("get_vertices_to_triangle_map", &TriangleMesh::get_vertices_to_triangle_map)
328
+ .def_property_readonly("vertex_to_triangle_offsets", &TriangleMesh::get_vertex_to_triangle_offsets)
329
+ .def_property_readonly("vertex_to_triangle_counts", &TriangleMesh::get_vertex_to_triangle_counts)
330
+ .def_property_readonly("vertex_to_triangle_indices", &TriangleMesh::get_vertex_to_triangle_indices)
331
+ .def("is_edge_manifold", &TriangleMesh::is_edge_manifold, py::arg("allow_boundary_edge") = true)
332
+ .def("is_vertex_manifold", &TriangleMesh::is_vertex_manifold)
333
+ .def("is_manifold", &TriangleMesh::is_manifold, py::arg("allow_boundary_edge") = true)
334
+ .def("get_non_manifold_vertices", &TriangleMesh::get_non_manifold_vertices)
335
+ .def("remove_triangles_by_mask", &TriangleMesh::remove_triangles_by_mask, py::arg("keep_mask"))
336
+ .def_property_readonly("euler_characteristic", &TriangleMesh::get_euler_characteristic)
337
+ .def_property_readonly("genus", &TriangleMesh::get_genus);
338
+ }