conquer3d 0.2.5__tar.gz → 0.2.6__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.
- {conquer3d-0.2.5/conquer3d.egg-info → conquer3d-0.2.6}/PKG-INFO +1 -1
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/_C.pyi +14 -2
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/_C.so +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/data_structure/bvh.cpp +69 -32
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +9 -1
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +22 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/bvh.cu +93 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/bvh.h +17 -1
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/mesh_bvh.cu +208 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/mesh_bvh.h +8 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/triangle_mesh.h +7 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/aabb.h +18 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/triangles.h +44 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6/conquer3d.egg-info}/PKG-INFO +1 -1
- {conquer3d-0.2.5 → conquer3d-0.2.6}/pyproject.toml +1 -1
- {conquer3d-0.2.5 → conquer3d-0.2.6}/LICENSE +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/MANIFEST.in +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/README.md +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/__init__.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/creation/__init__.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/creation/triangle_creation.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/creation/triangle_creation.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/check.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/constants.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/creation/triangle_creation.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/kdtree.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/triangle_mesh.cu +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/data_structure/zcurve.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/f2x2.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/f3x1.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/f3x3.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/f3x4.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/f4x1.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/f4x4.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/maths.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/maths/ops.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/edge.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/gs.cu +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/gs.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/pgs.cu +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/pgs.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/primitive/ray.h +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/csrc/pybind.cpp +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/data_structure/__init__.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/primitive/__init__.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/primitive/gs.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d/primitive/pgs.py +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d.egg-info/SOURCES.txt +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d.egg-info/dependency_links.txt +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d.egg-info/not-zip-safe +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d.egg-info/requires.txt +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/conquer3d.egg-info/top_level.txt +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/setup.cfg +0 -0
- {conquer3d-0.2.5 → conquer3d-0.2.6}/setup.py +0 -0
|
@@ -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
|
|
17
|
+
def query_point(self, query_points: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
18
18
|
"""
|
|
19
|
-
|
|
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
|
"""
|
|
@@ -65,6 +69,10 @@ class MeshBVH(BVH):
|
|
|
65
69
|
"""
|
|
66
70
|
Check if there are any self-intersecting triangle pairs
|
|
67
71
|
"""
|
|
72
|
+
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]:
|
|
73
|
+
"""
|
|
74
|
+
Find the closest triangle to each query point. Returns (query_ids, triangle_ids, projected_points, distances)
|
|
75
|
+
"""
|
|
68
76
|
class PGSBVH(BVH):
|
|
69
77
|
def __init__(self, in_aabb_mins: torch.Tensor, in_aabb_maxs: torch.Tensor) -> None:
|
|
70
78
|
"""
|
|
@@ -115,6 +123,10 @@ class TriangleMesh:
|
|
|
115
123
|
"""
|
|
116
124
|
def is_vertex_manifold(self) -> bool:
|
|
117
125
|
...
|
|
126
|
+
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]:
|
|
127
|
+
"""
|
|
128
|
+
Query points against the mesh. Returns (query_ids, triangle_ids, projected_points, distances)
|
|
129
|
+
"""
|
|
118
130
|
def remove_triangles_by_mask(self, keep_mask: torch.Tensor) -> None:
|
|
119
131
|
...
|
|
120
132
|
@property
|
|
Binary file
|
|
@@ -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.
|
|
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
|
-
|
|
168
|
-
auto options_int64 = ray_origins.options().dtype(torch::kInt64);
|
|
165
|
+
int num_queries = ray_origins.size(0);
|
|
169
166
|
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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,
|
|
198
|
-
out_object_ids.slice(0, 0,
|
|
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
|
-
"
|
|
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
|
}
|
|
@@ -32,5 +32,13 @@ void bind_ds_mesh_bvh(py::module_& m)
|
|
|
32
32
|
py::arg("vertices"),
|
|
33
33
|
py::arg("triangles"),
|
|
34
34
|
py::arg("return_distance") = false,
|
|
35
|
-
"Find all ray-triangle intersections. Returns (ray_ids, triangle_ids, intersect_points, [distances])")
|
|
35
|
+
"Find all ray-triangle intersections. Returns (ray_ids, triangle_ids, intersect_points, [distances])")
|
|
36
|
+
.def("query_point", &MeshBVH::query_point,
|
|
37
|
+
py::arg("query_points"),
|
|
38
|
+
py::arg("vertices"),
|
|
39
|
+
py::arg("triangles"),
|
|
40
|
+
py::arg("return_sdf") = false,
|
|
41
|
+
py::arg("return_prj_pts") = true,
|
|
42
|
+
py::arg("sign_mode") = 0,
|
|
43
|
+
"Find the closest triangle to each query point. Returns (query_ids, triangle_ids, projected_points, distances)");
|
|
36
44
|
}
|
|
@@ -97,6 +97,21 @@ bool TriangleMesh::is_self_intersection()
|
|
|
97
97
|
return this->bvh.value().is_self_intersection(this->vertices, this->triangles);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::query_points(
|
|
101
|
+
const torch::Tensor &query_pts,
|
|
102
|
+
bool return_sdf,
|
|
103
|
+
bool return_prj_pts,
|
|
104
|
+
int sign_mode,
|
|
105
|
+
int distance_mode)
|
|
106
|
+
{
|
|
107
|
+
if (distance_mode == 0) {
|
|
108
|
+
this->build_bvh();
|
|
109
|
+
return this->bvh.value().query_point(query_pts, this->vertices, this->triangles, return_sdf, return_prj_pts, sign_mode);
|
|
110
|
+
} else {
|
|
111
|
+
throw std::runtime_error("distance_mode != 0 not implemented yet");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> TriangleMesh::get_ray_intersection(
|
|
101
116
|
const torch::Tensor &ray_origins,
|
|
102
117
|
const torch::Tensor &ray_dirs,
|
|
@@ -338,6 +353,13 @@ void bind_ds_triangle_mesh(py::module_ &m)
|
|
|
338
353
|
py::arg("ray_dirs"),
|
|
339
354
|
py::arg("return_distance") = false,
|
|
340
355
|
"Find all ray-triangle intersections. Returns (ray_ids, triangle_ids, intersect_points, [distances])")
|
|
356
|
+
.def("query_points", &TriangleMesh::query_points,
|
|
357
|
+
py::arg("query_pts"),
|
|
358
|
+
py::arg("return_sdf") = false,
|
|
359
|
+
py::arg("return_prj_pts") = true,
|
|
360
|
+
py::arg("sign_mode") = 0,
|
|
361
|
+
py::arg("distance_mode") = 0,
|
|
362
|
+
"Query points against the mesh. Returns (query_ids, triangle_ids, projected_points, distances)")
|
|
341
363
|
.def_property_readonly("edges", &TriangleMesh::get_edges)
|
|
342
364
|
.def_property_readonly("edge_to_triangle_offsets", &TriangleMesh::get_edge_to_triangle_offsets)
|
|
343
365
|
.def_property_readonly("edge_to_triangle_counts", &TriangleMesh::get_edge_to_triangle_counts)
|
|
@@ -542,4 +542,97 @@ namespace bvh
|
|
|
542
542
|
max_capacity
|
|
543
543
|
);
|
|
544
544
|
}
|
|
545
|
+
|
|
546
|
+
__global__ void query_point_bvh_kernel(
|
|
547
|
+
const uint32_t num_queries,
|
|
548
|
+
const uint32_t num_objects,
|
|
549
|
+
const float3* __restrict__ query_points,
|
|
550
|
+
const float3* __restrict__ bvh_aabb_mins,
|
|
551
|
+
const float3* __restrict__ bvh_aabb_maxs,
|
|
552
|
+
const int2* __restrict__ bvh_children,
|
|
553
|
+
const int* __restrict__ object_ids,
|
|
554
|
+
int64_t* __restrict__ out_query_ids,
|
|
555
|
+
int64_t* __restrict__ out_object_ids,
|
|
556
|
+
float* __restrict__ out_distances)
|
|
557
|
+
{
|
|
558
|
+
uint32_t q_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
559
|
+
if (q_idx >= num_queries) return;
|
|
560
|
+
|
|
561
|
+
float3 p = query_points[q_idx];
|
|
562
|
+
|
|
563
|
+
int stack[BVH_STACK_SIZE];
|
|
564
|
+
int stack_ptr = 0;
|
|
565
|
+
stack[0] = 0;
|
|
566
|
+
|
|
567
|
+
float best_dist_sq = FLT_MAX;
|
|
568
|
+
int best_leaf_original_id = -1;
|
|
569
|
+
|
|
570
|
+
while (stack_ptr >= 0)
|
|
571
|
+
{
|
|
572
|
+
int node_idx = stack[stack_ptr--];
|
|
573
|
+
|
|
574
|
+
float dist_sq = aabb::compute_squared_distance(p, bvh_aabb_mins[node_idx], bvh_aabb_maxs[node_idx]);
|
|
575
|
+
|
|
576
|
+
if (dist_sq > best_dist_sq) continue; // Prune branch
|
|
577
|
+
|
|
578
|
+
if (node_idx >= num_objects - 1)
|
|
579
|
+
{
|
|
580
|
+
if (dist_sq < best_dist_sq) {
|
|
581
|
+
best_dist_sq = dist_sq;
|
|
582
|
+
best_leaf_original_id = object_ids[node_idx - (num_objects - 1)];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else
|
|
586
|
+
{
|
|
587
|
+
if (stack_ptr + 2 < BVH_STACK_SIZE)
|
|
588
|
+
{
|
|
589
|
+
int2 children = bvh_children[node_idx];
|
|
590
|
+
|
|
591
|
+
float dist_l = aabb::compute_squared_distance(p, bvh_aabb_mins[children.x], bvh_aabb_maxs[children.x]);
|
|
592
|
+
float dist_r = aabb::compute_squared_distance(p, bvh_aabb_mins[children.y], bvh_aabb_maxs[children.y]);
|
|
593
|
+
|
|
594
|
+
if (dist_l > dist_r) {
|
|
595
|
+
stack[++stack_ptr] = children.x;
|
|
596
|
+
stack[++stack_ptr] = children.y;
|
|
597
|
+
} else {
|
|
598
|
+
stack[++stack_ptr] = children.y;
|
|
599
|
+
stack[++stack_ptr] = children.x;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
out_query_ids[q_idx] = q_idx;
|
|
606
|
+
out_object_ids[q_idx] = best_leaf_original_id;
|
|
607
|
+
out_distances[q_idx] = sqrtf(best_dist_sq);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
void query_point(
|
|
611
|
+
const uint32_t num_queries,
|
|
612
|
+
const uint32_t num_objects,
|
|
613
|
+
const float3* __restrict__ query_points,
|
|
614
|
+
const float3* __restrict__ bvh_aabb_mins,
|
|
615
|
+
const float3* __restrict__ bvh_aabb_maxs,
|
|
616
|
+
const int2* __restrict__ bvh_children,
|
|
617
|
+
const int* __restrict__ object_ids,
|
|
618
|
+
int64_t* __restrict__ out_query_ids,
|
|
619
|
+
int64_t* __restrict__ out_object_ids,
|
|
620
|
+
float* __restrict__ out_distances)
|
|
621
|
+
{
|
|
622
|
+
uint32_t threads = NTHREADS;
|
|
623
|
+
uint32_t blocks = (num_queries + threads - 1) / threads;
|
|
624
|
+
|
|
625
|
+
query_point_bvh_kernel<<<blocks, threads>>>(
|
|
626
|
+
num_queries,
|
|
627
|
+
num_objects,
|
|
628
|
+
query_points,
|
|
629
|
+
bvh_aabb_mins,
|
|
630
|
+
bvh_aabb_maxs,
|
|
631
|
+
bvh_children,
|
|
632
|
+
object_ids,
|
|
633
|
+
out_query_ids,
|
|
634
|
+
out_object_ids,
|
|
635
|
+
out_distances
|
|
636
|
+
);
|
|
637
|
+
}
|
|
545
638
|
}
|
|
@@ -33,7 +33,11 @@ public:
|
|
|
33
33
|
|
|
34
34
|
std::tuple<torch::Tensor, torch::Tensor> query_ray(
|
|
35
35
|
const torch::Tensor &ray_origins,
|
|
36
|
-
const torch::Tensor &ray_dirs
|
|
36
|
+
const torch::Tensor &ray_dirs,
|
|
37
|
+
int64_t max_capacity = BVH_MAX_CAPACITY);
|
|
38
|
+
|
|
39
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> query_point(
|
|
40
|
+
const torch::Tensor &query_points);
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
namespace bvh
|
|
@@ -88,6 +92,18 @@ namespace bvh
|
|
|
88
92
|
int64_t *__restrict__ out_object_ids,
|
|
89
93
|
int64_t *__restrict__ hit_counter,
|
|
90
94
|
const int64_t max_capacity);
|
|
95
|
+
|
|
96
|
+
__host__ void query_point(
|
|
97
|
+
const uint32_t num_queries,
|
|
98
|
+
const uint32_t num_objects,
|
|
99
|
+
const float3 *__restrict__ query_points,
|
|
100
|
+
const float3 *__restrict__ bvh_aabb_mins,
|
|
101
|
+
const float3 *__restrict__ bvh_aabb_maxs,
|
|
102
|
+
const int2 *__restrict__ bvh_children,
|
|
103
|
+
const int *__restrict__ object_ids,
|
|
104
|
+
int64_t *__restrict__ out_query_ids,
|
|
105
|
+
int64_t *__restrict__ out_object_ids,
|
|
106
|
+
float *__restrict__ out_distances);
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
#endif // BVH_H
|
|
@@ -203,3 +203,211 @@ std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> MeshBVH::
|
|
|
203
203
|
return_distance ? out_distances.slice(0, 0, h_valid_counter) : out_distances
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
__device__ __forceinline__ float compute_sign_ray_parity(
|
|
208
|
+
const float3& p,
|
|
209
|
+
const int best_tri_id,
|
|
210
|
+
const float3* bvh_aabb_mins,
|
|
211
|
+
const float3* bvh_aabb_maxs,
|
|
212
|
+
const int2* bvh_children,
|
|
213
|
+
const int* object_ids,
|
|
214
|
+
const float3* vertices,
|
|
215
|
+
const int3* triangles,
|
|
216
|
+
const uint32_t num_objects)
|
|
217
|
+
{
|
|
218
|
+
int3 tri = triangles[best_tri_id];
|
|
219
|
+
float3 v0 = vertices[tri.x];
|
|
220
|
+
float3 v1 = vertices[tri.y];
|
|
221
|
+
float3 v2 = vertices[tri.z];
|
|
222
|
+
float3 centroid = (v0 + v1 + v2) / 3.0f;
|
|
223
|
+
|
|
224
|
+
float3 ray_dir = centroid - p;
|
|
225
|
+
float dir_len = maths::norm(ray_dir);
|
|
226
|
+
if (dir_len > 1e-6f) {
|
|
227
|
+
ray_dir = ray_dir / dir_len;
|
|
228
|
+
} else {
|
|
229
|
+
ray_dir = make_float3(1.0f, 0.0f, 0.0f);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Ray ray2(p, ray_dir, 0.0f);
|
|
233
|
+
|
|
234
|
+
int hit_count = 0;
|
|
235
|
+
int stack[BVH_STACK_SIZE];
|
|
236
|
+
int stack_ptr = 0;
|
|
237
|
+
stack[0] = 0;
|
|
238
|
+
|
|
239
|
+
while (stack_ptr >= 0)
|
|
240
|
+
{
|
|
241
|
+
int node_idx = stack[stack_ptr--];
|
|
242
|
+
|
|
243
|
+
float t_hit_aabb;
|
|
244
|
+
if (!ray2.is_intersect_aabb(bvh_aabb_mins[node_idx], bvh_aabb_maxs[node_idx], t_hit_aabb)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (node_idx >= num_objects - 1)
|
|
249
|
+
{
|
|
250
|
+
int tri_id = object_ids[node_idx - (num_objects - 1)];
|
|
251
|
+
int3 tri = triangles[tri_id];
|
|
252
|
+
Triangle T(vertices[tri.x], vertices[tri.y], vertices[tri.z]);
|
|
253
|
+
|
|
254
|
+
float t_hit, u, v;
|
|
255
|
+
if (T.is_intersect_ray(ray2, t_hit, u, v)) {
|
|
256
|
+
hit_count++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else
|
|
260
|
+
{
|
|
261
|
+
if (stack_ptr + 2 < BVH_STACK_SIZE)
|
|
262
|
+
{
|
|
263
|
+
int2 children = bvh_children[node_idx];
|
|
264
|
+
stack[++stack_ptr] = children.x;
|
|
265
|
+
stack[++stack_ptr] = children.y;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (hit_count % 2 == 1) ? -1.0f : 1.0f;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
__global__ void query_point_mesh_bvh_kernel(
|
|
274
|
+
const uint32_t num_queries,
|
|
275
|
+
const uint32_t num_objects,
|
|
276
|
+
const float3* __restrict__ query_points,
|
|
277
|
+
const float3* __restrict__ vertices,
|
|
278
|
+
const int3* __restrict__ triangles,
|
|
279
|
+
const float3* __restrict__ bvh_aabb_mins,
|
|
280
|
+
const float3* __restrict__ bvh_aabb_maxs,
|
|
281
|
+
const int2* __restrict__ bvh_children,
|
|
282
|
+
const int* __restrict__ object_ids,
|
|
283
|
+
int64_t* __restrict__ out_query_ids,
|
|
284
|
+
int64_t* __restrict__ out_object_ids,
|
|
285
|
+
float3* __restrict__ out_projected_pts,
|
|
286
|
+
float* __restrict__ out_distances,
|
|
287
|
+
bool return_sdf,
|
|
288
|
+
bool return_prj_pts,
|
|
289
|
+
int sign_mode)
|
|
290
|
+
{
|
|
291
|
+
uint32_t q_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
292
|
+
if (q_idx >= num_queries) return;
|
|
293
|
+
|
|
294
|
+
float3 p = query_points[q_idx];
|
|
295
|
+
|
|
296
|
+
int stack[BVH_STACK_SIZE];
|
|
297
|
+
int stack_ptr = 0;
|
|
298
|
+
stack[0] = 0;
|
|
299
|
+
|
|
300
|
+
float best_dist_sq = FLT_MAX;
|
|
301
|
+
int best_tri_id = -1;
|
|
302
|
+
float3 best_pt = make_float3(0, 0, 0);
|
|
303
|
+
|
|
304
|
+
while (stack_ptr >= 0)
|
|
305
|
+
{
|
|
306
|
+
int node_idx = stack[stack_ptr--];
|
|
307
|
+
|
|
308
|
+
float dist_sq = aabb::compute_squared_distance(p, bvh_aabb_mins[node_idx], bvh_aabb_maxs[node_idx]);
|
|
309
|
+
|
|
310
|
+
if (dist_sq > best_dist_sq) continue; // Prune branch
|
|
311
|
+
|
|
312
|
+
if (node_idx >= num_objects - 1)
|
|
313
|
+
{
|
|
314
|
+
int tri_id = object_ids[node_idx - (num_objects - 1)];
|
|
315
|
+
int3 tri = triangles[tri_id];
|
|
316
|
+
Triangle T(vertices[tri.x], vertices[tri.y], vertices[tri.z]);
|
|
317
|
+
|
|
318
|
+
float3 closest_pt = T.compute_closest_point(p);
|
|
319
|
+
float3 diff = p - closest_pt;
|
|
320
|
+
float pt_dist_sq = maths::dot(diff, diff);
|
|
321
|
+
|
|
322
|
+
if (pt_dist_sq < best_dist_sq) {
|
|
323
|
+
best_dist_sq = pt_dist_sq;
|
|
324
|
+
best_tri_id = tri_id;
|
|
325
|
+
best_pt = closest_pt;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else
|
|
329
|
+
{
|
|
330
|
+
if (stack_ptr + 2 < BVH_STACK_SIZE)
|
|
331
|
+
{
|
|
332
|
+
int2 children = bvh_children[node_idx];
|
|
333
|
+
|
|
334
|
+
float dist_l = aabb::compute_squared_distance(p, bvh_aabb_mins[children.x], bvh_aabb_maxs[children.x]);
|
|
335
|
+
float dist_r = aabb::compute_squared_distance(p, bvh_aabb_mins[children.y], bvh_aabb_maxs[children.y]);
|
|
336
|
+
|
|
337
|
+
if (dist_l > dist_r) {
|
|
338
|
+
stack[++stack_ptr] = children.x;
|
|
339
|
+
stack[++stack_ptr] = children.y;
|
|
340
|
+
} else {
|
|
341
|
+
stack[++stack_ptr] = children.y;
|
|
342
|
+
stack[++stack_ptr] = children.x;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
float dist = sqrtf(best_dist_sq);
|
|
349
|
+
|
|
350
|
+
if (return_sdf && sign_mode == 0) {
|
|
351
|
+
dist *= compute_sign_ray_parity(p, best_tri_id, bvh_aabb_mins, bvh_aabb_maxs, bvh_children, object_ids, vertices, triangles, num_objects);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
out_query_ids[q_idx] = q_idx;
|
|
355
|
+
out_object_ids[q_idx] = best_tri_id;
|
|
356
|
+
if (return_prj_pts && out_projected_pts != nullptr) {
|
|
357
|
+
out_projected_pts[q_idx] = best_pt;
|
|
358
|
+
}
|
|
359
|
+
out_distances[q_idx] = dist;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> MeshBVH::query_point(
|
|
363
|
+
const torch::Tensor &query_points,
|
|
364
|
+
const torch::Tensor &vertices,
|
|
365
|
+
const torch::Tensor &triangles,
|
|
366
|
+
bool return_sdf,
|
|
367
|
+
bool return_prj_pts,
|
|
368
|
+
int sign_mode)
|
|
369
|
+
{
|
|
370
|
+
if (sign_mode != 0) {
|
|
371
|
+
throw std::runtime_error("sign_mode != 0 not implemented yet");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
int num_queries = query_points.size(0);
|
|
375
|
+
int num_objects = this->object_ids.size(0);
|
|
376
|
+
|
|
377
|
+
auto options_i64 = torch::TensorOptions().dtype(torch::kInt64).device(query_points.device());
|
|
378
|
+
auto options_f32 = torch::TensorOptions().dtype(torch::kFloat32).device(query_points.device());
|
|
379
|
+
|
|
380
|
+
torch::Tensor out_query_ids = torch::empty({num_queries}, options_i64);
|
|
381
|
+
torch::Tensor out_object_ids = torch::empty({num_queries}, options_i64);
|
|
382
|
+
torch::Tensor out_projected_pts;
|
|
383
|
+
if (return_prj_pts) {
|
|
384
|
+
out_projected_pts = torch::empty({num_queries, 3}, options_f32);
|
|
385
|
+
} else {
|
|
386
|
+
out_projected_pts = torch::empty({0, 3}, options_f32);
|
|
387
|
+
}
|
|
388
|
+
torch::Tensor out_distances = torch::empty({num_queries}, options_f32);
|
|
389
|
+
|
|
390
|
+
int threads = NTHREADS;
|
|
391
|
+
int blocks = (num_queries + threads - 1) / threads;
|
|
392
|
+
|
|
393
|
+
query_point_mesh_bvh_kernel<<<blocks, threads>>>(
|
|
394
|
+
num_queries,
|
|
395
|
+
num_objects,
|
|
396
|
+
(const float3*)query_points.data_ptr<float>(),
|
|
397
|
+
(const float3*)vertices.data_ptr<float>(),
|
|
398
|
+
(const int3*)triangles.data_ptr<int>(),
|
|
399
|
+
(const float3*)this->aabb_mins.data_ptr<float>(),
|
|
400
|
+
(const float3*)this->aabb_maxs.data_ptr<float>(),
|
|
401
|
+
(const int2*)this->bvh_children.data_ptr<int>(),
|
|
402
|
+
this->object_ids.data_ptr<int>(),
|
|
403
|
+
out_query_ids.data_ptr<int64_t>(),
|
|
404
|
+
out_object_ids.data_ptr<int64_t>(),
|
|
405
|
+
return_prj_pts ? (float3*)out_projected_pts.data_ptr<float>() : nullptr,
|
|
406
|
+
out_distances.data_ptr<float>(),
|
|
407
|
+
return_sdf,
|
|
408
|
+
return_prj_pts,
|
|
409
|
+
sign_mode
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return std::make_tuple(out_query_ids, out_object_ids, out_projected_pts, out_distances);
|
|
413
|
+
}
|
|
@@ -28,6 +28,14 @@ public:
|
|
|
28
28
|
const torch::Tensor &vertices,
|
|
29
29
|
const torch::Tensor &triangles,
|
|
30
30
|
bool return_distance = false);
|
|
31
|
+
|
|
32
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> query_point(
|
|
33
|
+
const torch::Tensor &query_points,
|
|
34
|
+
const torch::Tensor &vertices,
|
|
35
|
+
const torch::Tensor &triangles,
|
|
36
|
+
bool return_sdf = false,
|
|
37
|
+
bool return_prj_pts = true,
|
|
38
|
+
int sign_mode = 0);
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
#endif // MESH_BVH_H
|
|
@@ -61,6 +61,13 @@ public:
|
|
|
61
61
|
torch::Tensor get_self_intersection();
|
|
62
62
|
bool is_self_intersection();
|
|
63
63
|
|
|
64
|
+
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> query_points(
|
|
65
|
+
const torch::Tensor &query_pts,
|
|
66
|
+
bool return_sdf = false,
|
|
67
|
+
bool return_prj_pts = true,
|
|
68
|
+
int sign_mode = 0,
|
|
69
|
+
int distance_mode = 0);
|
|
70
|
+
|
|
64
71
|
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor> get_ray_intersection(
|
|
65
72
|
const torch::Tensor &ray_origins,
|
|
66
73
|
const torch::Tensor &ray_dirs,
|
|
@@ -112,6 +112,24 @@ namespace aabb
|
|
|
112
112
|
out_surface_area = 2.0f * (dim_size.x * dim_size.y + dim_size.x * dim_size.z + dim_size.y * dim_size.z);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
__device__ __forceinline__ float compute_squared_distance(
|
|
116
|
+
const float3 &p,
|
|
117
|
+
const float3 &aabb_min,
|
|
118
|
+
const float3 &aabb_max)
|
|
119
|
+
{
|
|
120
|
+
float d2 = 0.0f;
|
|
121
|
+
if (p.x < aabb_min.x) d2 += (aabb_min.x - p.x) * (aabb_min.x - p.x);
|
|
122
|
+
else if (p.x > aabb_max.x) d2 += (p.x - aabb_max.x) * (p.x - aabb_max.x);
|
|
123
|
+
|
|
124
|
+
if (p.y < aabb_min.y) d2 += (aabb_min.y - p.y) * (aabb_min.y - p.y);
|
|
125
|
+
else if (p.y > aabb_max.y) d2 += (p.y - aabb_max.y) * (p.y - aabb_max.y);
|
|
126
|
+
|
|
127
|
+
if (p.z < aabb_min.z) d2 += (aabb_min.z - p.z) * (aabb_min.z - p.z);
|
|
128
|
+
else if (p.z > aabb_max.z) d2 += (p.z - aabb_max.z) * (p.z - aabb_max.z);
|
|
129
|
+
|
|
130
|
+
return d2;
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
__device__ __forceinline__ bool test_aabb_overlap(
|
|
116
134
|
const float3 &query_aabb_min,
|
|
117
135
|
const float3 &query_aabb_max,
|
|
@@ -43,6 +43,50 @@ struct Triangle {
|
|
|
43
43
|
}
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
__host__ __device__ __forceinline__ float3 compute_closest_point(const float3& p) const {
|
|
48
|
+
float3 ab = v1 - v0;
|
|
49
|
+
float3 ac = v2 - v0;
|
|
50
|
+
float3 ap = p - v0;
|
|
51
|
+
|
|
52
|
+
float d1 = maths::dot(ab, ap);
|
|
53
|
+
float d2 = maths::dot(ac, ap);
|
|
54
|
+
if (d1 <= 0.0f && d2 <= 0.0f) return v0;
|
|
55
|
+
|
|
56
|
+
float3 bp = p - v1;
|
|
57
|
+
float d3 = maths::dot(ab, bp);
|
|
58
|
+
float d4 = maths::dot(ac, bp);
|
|
59
|
+
if (d3 >= 0.0f && d4 <= d3) return v1;
|
|
60
|
+
|
|
61
|
+
float vc = d1*d4 - d3*d2;
|
|
62
|
+
if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) {
|
|
63
|
+
float v = d1 / (d1 - d3);
|
|
64
|
+
return v0 + v * ab;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
float3 cp = p - v2;
|
|
68
|
+
float d5 = maths::dot(ab, cp);
|
|
69
|
+
float d6 = maths::dot(ac, cp);
|
|
70
|
+
if (d6 >= 0.0f && d5 <= d6) return v2;
|
|
71
|
+
|
|
72
|
+
float vb = d5*d2 - d1*d6;
|
|
73
|
+
if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) {
|
|
74
|
+
float w = d2 / (d2 - d6);
|
|
75
|
+
return v0 + w * ac;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
float va = d3*d6 - d5*d4;
|
|
79
|
+
if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) {
|
|
80
|
+
float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
|
81
|
+
return v1 + w * (v2 - v1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
float denom = 1.0f / (va + vb + vc);
|
|
85
|
+
float v = vb * denom;
|
|
86
|
+
float w = vc * denom;
|
|
87
|
+
return v0 + ab * v + ac * w;
|
|
88
|
+
}
|
|
89
|
+
|
|
46
90
|
__host__ __device__ __forceinline__ float3 compute_normal() const {
|
|
47
91
|
float3 edge1 = v1 - v0;
|
|
48
92
|
float3 edge2 = v2 - v0;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|