ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
TriangleMesh.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <fmt/core.h>
11 // Qt defines a macro named 'emit' which conflicts with oneTBB's profiling API.
12 #ifdef emit
13 #undef emit
14 #endif
15 #include <tbb/parallel_for_each.h>
16 #include <vtkBooleanOperationPolyDataFilter.h>
17 #include <vtkCleanPolyData.h>
18 #include <vtkClipPolyData.h>
19 #include <vtkCutter.h>
20 #include <vtkFillHolesFilter.h>
21 #include <vtkPlane.h>
22 #include <vtkQuadricDecimation.h>
23 
26 
27 #include <Helper.h>
28 
29 #include <Eigen/Core>
30 #include <algorithm>
31 #include <cmath>
32 #include <cstddef>
33 #include <memory>
34 #include <mutex>
35 #include <string>
36 #include <tuple>
37 #include <unordered_map>
38 #include <unordered_set>
39 #include <vector>
40 
43 #include "cloudViewer/core/Dtype.h"
67 
68 namespace cloudViewer {
69 namespace t {
70 namespace geometry {
71 
72 class PointCloud; // forward declaration
73 
76  device_(device),
77  vertex_attr_(TensorMap("positions")),
78  triangle_attr_(TensorMap("indices")) {}
79 
80 TriangleMesh::TriangleMesh(const core::Tensor &vertex_positions,
81  const core::Tensor &triangle_indices)
82  : TriangleMesh([&]() {
83  if (vertex_positions.GetDevice() != triangle_indices.GetDevice()) {
85  "vertex_positions' device {} does not match "
86  "triangle_indices' device {}.",
87  vertex_positions.GetDevice().ToString(),
88  triangle_indices.GetDevice().ToString());
89  }
90  return vertex_positions.GetDevice();
91  }()) {
92  SetVertexPositions(vertex_positions);
93  SetTriangleIndices(triangle_indices);
94 }
95 
96 std::string TriangleMesh::ToString() const {
97  size_t num_vertices = 0;
98  std::string vertex_dtype_str = "";
99  size_t num_triangles = 0;
100  std::string triangles_dtype_str = "";
101  if (vertex_attr_.count(vertex_attr_.GetPrimaryKey())) {
102  num_vertices = GetVertexPositions().GetLength();
103  vertex_dtype_str = fmt::format(
104  " ({})", GetVertexPositions().GetDtype().ToString());
105  }
107  num_triangles = GetTriangleIndices().GetLength();
108  triangles_dtype_str = fmt::format(
109  " ({})", GetTriangleIndices().GetDtype().ToString());
110  }
111 
112  auto str = fmt::format(
113  "TriangleMesh on {} [{} vertices{} and {} triangles{}].",
114  GetDevice().ToString(), num_vertices, vertex_dtype_str,
115  num_triangles, triangles_dtype_str);
116 
117  std::string vertices_attr_str = "\nVertex Attributes:";
118  if ((vertex_attr_.size() -
119  vertex_attr_.count(vertex_attr_.GetPrimaryKey())) == 0) {
120  vertices_attr_str += " None.";
121  } else {
122  for (const auto &kv : vertex_attr_) {
123  if (kv.first != "positions") {
124  vertices_attr_str +=
125  fmt::format(" {} (dtype = {}, shape = {}),", kv.first,
126  kv.second.GetDtype().ToString(),
127  kv.second.GetShape().ToString());
128  }
129  }
130  vertices_attr_str[vertices_attr_str.size() - 1] = '.';
131  }
132 
133  std::string triangles_attr_str = "\nTriangle Attributes:";
134  if ((triangle_attr_.size() -
136  triangles_attr_str += " None.";
137  } else {
138  for (const auto &kv : triangle_attr_) {
139  if (kv.first != "indices") {
140  triangles_attr_str +=
141  fmt::format(" {} (dtype = {}, shape = {}),", kv.first,
142  kv.second.GetDtype().ToString(),
143  kv.second.GetShape().ToString());
144  }
145  }
146  triangles_attr_str[triangles_attr_str.size() - 1] = '.';
147  }
148 
149  return str + vertices_attr_str + triangles_attr_str;
150 }
151 
153  core::AssertTensorShape(transformation, {4, 4});
154 
156  if (HasVertexNormals()) {
158  }
159  if (HasTriangleNormals()) {
162  }
163 
164  return *this;
165 }
166 
168  bool relative) {
169  core::AssertTensorShape(translation, {3});
170 
171  core::Tensor transform =
172  translation.To(GetDevice(), GetVertexPositions().GetDtype());
173 
174  if (!relative) {
175  transform -= GetCenter();
176  }
177  GetVertexPositions() += transform;
178  return *this;
179 }
180 
181 TriangleMesh &TriangleMesh::Scale(double scale, const core::Tensor &center) {
182  core::AssertTensorShape(center, {3});
184 
185  const core::Tensor center_d =
186  center.To(GetDevice(), GetVertexPositions().GetDtype());
187 
188  GetVertexPositions().Sub_(center_d).Mul_(scale).Add_(center_d);
189  return *this;
190 }
191 
193  const core::Tensor &center) {
194  core::AssertTensorShape(R, {3, 3});
195  core::AssertTensorShape(center, {3});
196 
198  if (HasVertexNormals()) {
200  }
201  if (HasTriangleNormals()) {
203  }
204  return *this;
205 }
206 
208  if (HasVertexNormals()) {
209  SetVertexNormals(GetVertexNormals().Contiguous());
210  core::Tensor &vertex_normals = GetVertexNormals();
211  if (IsCPU()) {
213  } else if (IsCUDA()) {
214  CUDA_CALL(kernel::trianglemesh::NormalizeNormalsCUDA,
215  vertex_normals);
216  } else {
217  utility::LogError("Unimplemented device");
218  }
219  } else {
220  utility::LogWarning("TriangleMesh has no vertex normals.");
221  }
222 
223  if (HasTriangleNormals()) {
224  SetTriangleNormals(GetTriangleNormals().Contiguous());
225  core::Tensor &triangle_normals = GetTriangleNormals();
226  if (IsCPU()) {
228  } else if (IsCUDA()) {
229  CUDA_CALL(kernel::trianglemesh::NormalizeNormalsCUDA,
230  triangle_normals);
231  } else {
232  utility::LogError("Unimplemented device");
233  }
234  } else {
235  utility::LogWarning("TriangleMesh has no triangle normals.");
236  }
237 
238  return *this;
239 }
240 
242  if (IsEmpty()) {
243  utility::LogWarning("TriangleMesh is empty.");
244  return *this;
245  }
246 
247  if (!HasTriangleIndices()) {
248  utility::LogWarning("TriangleMesh has no triangle indices.");
249  return *this;
250  }
251 
252  const int64_t triangle_num = GetTriangleIndices().GetLength();
253  const core::Dtype dtype = GetVertexPositions().GetDtype();
254  core::Tensor triangle_normals({triangle_num, 3}, dtype, GetDevice());
255  SetVertexPositions(GetVertexPositions().Contiguous());
256  SetTriangleIndices(GetTriangleIndices().Contiguous());
257 
258  if (IsCPU()) {
260  GetVertexPositions(), GetTriangleIndices(), triangle_normals);
261  } else if (IsCUDA()) {
262  CUDA_CALL(kernel::trianglemesh::ComputeTriangleNormalsCUDA,
263  GetVertexPositions(), GetTriangleIndices(), triangle_normals);
264  } else {
265  utility::LogError("Unimplemented device");
266  }
267 
268  SetTriangleNormals(triangle_normals);
269 
270  if (normalized) {
272  }
273 
274  return *this;
275 }
276 
278  if (IsEmpty()) {
279  utility::LogWarning("TriangleMesh is empty.");
280  return *this;
281  }
282 
283  if (!HasTriangleIndices()) {
284  utility::LogWarning("TriangleMesh has no triangle indices.");
285  return *this;
286  }
287 
288  ComputeTriangleNormals(false);
289 
290  const int64_t vertex_num = GetVertexPositions().GetLength();
291  const core::Dtype dtype = GetVertexPositions().GetDtype();
292  core::Tensor vertex_normals =
293  core::Tensor::Zeros({vertex_num, 3}, dtype, GetDevice());
294 
295  SetTriangleNormals(GetTriangleNormals().Contiguous());
296  SetTriangleIndices(GetTriangleIndices().Contiguous());
297 
298  if (IsCPU()) {
300  GetTriangleIndices(), GetTriangleNormals(), vertex_normals);
301  } else if (IsCUDA()) {
302  CUDA_CALL(kernel::trianglemesh::ComputeVertexNormalsCUDA,
303  GetTriangleIndices(), GetTriangleNormals(), vertex_normals);
304  } else {
305  utility::LogError("Unimplemented device");
306  }
307 
308  SetVertexNormals(vertex_normals);
309  if (normalized) {
311  }
312 
313  return *this;
314 }
315 
317  const int64_t triangle_num = mesh.GetTriangleIndices().GetLength();
318  const core::Dtype dtype = mesh.GetVertexPositions().GetDtype();
319  core::Tensor triangle_areas({triangle_num}, dtype, mesh.GetDevice());
320  if (mesh.IsCPU()) {
323  mesh.GetTriangleIndices().Contiguous(), triangle_areas);
324  } else if (mesh.IsCUDA()) {
325  CUDA_CALL(kernel::trianglemesh::ComputeTriangleAreasCUDA,
327  mesh.GetTriangleIndices().Contiguous(), triangle_areas);
328  } else {
329  utility::LogError("Unimplemented device");
330  }
331 
332  return triangle_areas;
333 }
334 
336  if (IsEmpty()) {
337  utility::LogWarning("TriangleMesh is empty.");
338  return *this;
339  }
340 
341  if (!HasTriangleIndices()) {
343  {0}, GetVertexPositions().GetDtype(),
344  GetDevice()));
345  utility::LogWarning("TriangleMesh has no triangle indices.");
346  return *this;
347  }
348  if (HasTriangleAttr("areas")) {
350  "TriangleMesh already has triangle areas: remove "
351  "'areas' triangle attribute if you'd like to update.");
352  return *this;
353  }
354 
355  core::Tensor triangle_areas = ComputeTriangleAreasHelper(*this);
356  SetTriangleAttr("areas", triangle_areas);
357 
358  return *this;
359 }
360 
362  double surface_area = 0;
363  if (IsEmpty()) {
364  utility::LogWarning("TriangleMesh is empty.");
365  return surface_area;
366  }
367 
368  if (!HasTriangleIndices()) {
369  utility::LogWarning("TriangleMesh has no triangle indices.");
370  return surface_area;
371  }
372 
373  core::Tensor triangle_areas = ComputeTriangleAreasHelper(*this);
374  surface_area = triangle_areas.Sum({0}).To(core::Float64).Item<double>();
375 
376  return surface_area;
377 }
378 
380  core::Dtype float_dtype,
381  core::Dtype int_dtype,
382  const core::Device &device) {
383  if (float_dtype != core::Float32 && float_dtype != core::Float64) {
384  utility::LogError("float_dtype must be Float32 or Float64, but got {}.",
385  float_dtype.ToString());
386  }
387  if (int_dtype != core::Int32 && int_dtype != core::Int64) {
388  utility::LogError("int_dtype must be Int32 or Int64, but got {}.",
389  int_dtype.ToString());
390  }
391 
392  TriangleMesh mesh(device);
393  if (mesh_legacy.HasVertices()) {
394  mesh.SetVertexPositions(
396  mesh_legacy.getEigenVertices(), float_dtype, device));
397  } else {
398  utility::LogWarning("Creating from empty legacy TriangleMesh.");
399  }
400  {
401  const auto colors = mesh_legacy.getVertexColors();
402  if (!colors.empty()) {
403  mesh.SetVertexColors(
405  colors, float_dtype, device));
406  }
407  }
408  {
409  const auto normals = mesh_legacy.getVertexNormals();
410  if (!normals.empty()) {
411  mesh.SetVertexNormals(
413  normals, float_dtype, device));
414  }
415  }
416  {
417  // triangles
418  const size_t tri_count = mesh_legacy.size();
419  if (tri_count > 0) {
420  std::vector<Eigen::Vector3i> tris;
421  tris.reserve(tri_count);
422  for (size_t i = 0; i < tri_count; ++i) {
423  Eigen::Vector3i t;
424  mesh_legacy.getTriangleVertIndexes(i, t);
425  tris.push_back(t);
426  }
427  mesh.SetTriangleIndices(
429  tris, int_dtype, device));
430  }
431  }
432  if (mesh_legacy.hasTriangleUvs()) {
433  mesh.SetTriangleAttr(
434  "texture_uvs",
436  mesh_legacy.triangle_uvs_, float_dtype, device)
437  .Reshape({-1, 3, 2}));
438  }
439 
440  // Convert first material only if one or more are present
441  if (mesh_legacy.materials_.size() > 0) {
442  const auto &mat = mesh_legacy.materials_.begin()->second;
443  auto &tmat = mesh.GetMaterial();
444  tmat.SetDefaultProperties();
445  tmat.SetBaseColor(Eigen::Vector4f{mat.baseColor.f4});
446  tmat.SetBaseRoughness(mat.baseRoughness);
447  tmat.SetBaseMetallic(mat.baseMetallic);
448  tmat.SetBaseReflectance(mat.baseReflectance);
449  tmat.SetAnisotropy(mat.baseAnisotropy);
450  tmat.SetBaseClearcoat(mat.baseClearCoat);
451  tmat.SetBaseClearcoatRoughness(mat.baseClearCoatRoughness);
452  // no emissive_color in legacy mesh material
453  if (mat.albedo) tmat.SetAlbedoMap(Image::FromLegacy(*mat.albedo));
454  if (mat.normalMap) tmat.SetNormalMap(Image::FromLegacy(*mat.normalMap));
455  if (mat.roughness)
456  tmat.SetRoughnessMap(Image::FromLegacy(*mat.roughness));
457  if (mat.metallic) tmat.SetMetallicMap(Image::FromLegacy(*mat.metallic));
458  if (mat.reflectance)
459  tmat.SetReflectanceMap(Image::FromLegacy(*mat.reflectance));
460  if (mat.ambientOcclusion)
461  tmat.SetAOMap(Image::FromLegacy(*mat.ambientOcclusion));
462  if (mat.clearCoat)
463  tmat.SetClearcoatMap(Image::FromLegacy(*mat.clearCoat));
464  if (mat.clearCoatRoughness)
465  tmat.SetClearcoatRoughnessMap(
466  Image::FromLegacy(*mat.clearCoatRoughness));
467  if (mat.anisotropy)
468  tmat.SetAnisotropyMap(Image::FromLegacy(*mat.anisotropy));
469  }
470  if (mesh_legacy.materials_.size() > 1) {
472  "Legacy mesh has more than 1 material which is not supported "
473  "by Tensor-based mesh. Only material {} was converted.",
474  mesh_legacy.materials_.begin()->first);
475  }
476  return mesh;
477 }
478 
480  ccMesh mesh_legacy;
481  mesh_legacy.CreateInternalCloud();
482  if (HasVertexPositions()) {
485  mesh_legacy.addEigenVertices(verts);
486  }
487  if (HasVertexColors()) {
488  mesh_legacy.addVertexColors(
490  GetVertexColors()));
491  }
492  if (HasVertexNormals()) {
493  mesh_legacy.addVertexNormals(
495  GetVertexNormals()));
496  }
497  if (HasTriangleIndices()) {
500  for (const auto &t : tris) {
501  mesh_legacy.addTriangle(static_cast<unsigned>(t(0)),
502  static_cast<unsigned>(t(1)),
503  static_cast<unsigned>(t(2)));
504  }
505  }
506  if (HasTriangleAttr("texture_uvs")) {
507  mesh_legacy.triangle_uvs_ =
509  GetTriangleAttr("texture_uvs").Reshape({-1, 2}));
510  }
511  if (HasVertexAttr("texture_uvs")) {
512  utility::LogWarning("{}",
513  "texture_uvs as a vertex attribute is not "
514  "supported by legacy TriangleMesh. Ignored.");
515  }
516 
517  // Convert material if the t geometry has a valid one
518  auto &tmat = GetMaterial();
519  if (tmat.IsValid()) {
520  mesh_legacy.materials_.emplace_back();
521  mesh_legacy.materials_.front().first = "Mat1";
522  auto &legacy_mat = mesh_legacy.materials_.front().second;
523  // Convert scalar properties
524  if (tmat.HasBaseColor()) {
525  legacy_mat.baseColor.f4[0] = tmat.GetBaseColor().x();
526  legacy_mat.baseColor.f4[1] = tmat.GetBaseColor().y();
527  legacy_mat.baseColor.f4[2] = tmat.GetBaseColor().z();
528  legacy_mat.baseColor.f4[3] = tmat.GetBaseColor().w();
529  }
530  if (tmat.HasBaseRoughness()) {
531  legacy_mat.baseRoughness = tmat.GetBaseRoughness();
532  }
533  if (tmat.HasBaseMetallic()) {
534  legacy_mat.baseMetallic = tmat.GetBaseMetallic();
535  }
536  if (tmat.HasBaseReflectance()) {
537  legacy_mat.baseReflectance = tmat.GetBaseReflectance();
538  }
539  if (tmat.HasBaseClearcoat()) {
540  legacy_mat.baseClearCoat = tmat.GetBaseClearcoat();
541  }
542  if (tmat.HasBaseClearcoatRoughness()) {
543  legacy_mat.baseClearCoatRoughness =
544  tmat.GetBaseClearcoatRoughness();
545  }
546  if (tmat.HasAnisotropy()) {
547  legacy_mat.baseAnisotropy = tmat.GetAnisotropy();
548  }
549  // Convert maps
550  if (tmat.HasAlbedoMap()) {
551  legacy_mat.albedo =
552  std::make_shared<cloudViewer::geometry::Image>();
553  *legacy_mat.albedo = tmat.GetAlbedoMap().ToLegacy();
554  }
555  if (tmat.HasNormalMap()) {
556  legacy_mat.normalMap =
557  std::make_shared<cloudViewer::geometry::Image>();
558  *legacy_mat.normalMap = tmat.GetNormalMap().ToLegacy();
559  }
560  if (tmat.HasAOMap()) {
561  legacy_mat.ambientOcclusion =
562  std::make_shared<cloudViewer::geometry::Image>();
563  *legacy_mat.ambientOcclusion = tmat.GetAOMap().ToLegacy();
564  }
565  if (tmat.HasMetallicMap()) {
566  legacy_mat.metallic =
567  std::make_shared<cloudViewer::geometry::Image>();
568  *legacy_mat.metallic = tmat.GetMetallicMap().ToLegacy();
569  }
570  if (tmat.HasRoughnessMap()) {
571  legacy_mat.roughness =
572  std::make_shared<cloudViewer::geometry::Image>();
573  *legacy_mat.roughness = tmat.GetRoughnessMap().ToLegacy();
574  }
575  if (tmat.HasReflectanceMap()) {
576  legacy_mat.reflectance =
577  std::make_shared<cloudViewer::geometry::Image>();
578  *legacy_mat.reflectance = tmat.GetReflectanceMap().ToLegacy();
579  }
580  if (tmat.HasClearcoatMap()) {
581  legacy_mat.clearCoat =
582  std::make_shared<cloudViewer::geometry::Image>();
583  *legacy_mat.clearCoat = tmat.GetClearcoatMap().ToLegacy();
584  }
585  if (tmat.HasClearcoatRoughnessMap()) {
586  legacy_mat.clearCoatRoughness =
587  std::make_shared<cloudViewer::geometry::Image>();
588  *legacy_mat.clearCoatRoughness =
589  tmat.GetClearcoatRoughnessMap().ToLegacy();
590  }
591  if (tmat.HasAnisotropyMap()) {
592  legacy_mat.anisotropy =
593  std::make_shared<cloudViewer::geometry::Image>();
594  *legacy_mat.anisotropy = tmat.GetAnisotropyMap().ToLegacy();
595  }
596  }
597 
598  return mesh_legacy;
599 }
600 
601 std::unordered_map<std::string, geometry::TriangleMesh>
604  core::Dtype float_dtype,
605  core::Dtype int_dtype,
606  const core::Device &device) {
607  std::unordered_map<std::string, TriangleMesh> tmeshes;
608  for (const auto &mobj : model.meshes_) {
609  auto tmesh = TriangleMesh::FromLegacy(*mobj.mesh, float_dtype,
610  int_dtype, device);
611  // material textures will be on the CPU. GPU resident texture images is
612  // not yet supported. See comment in Material.cpp
613  tmesh.SetMaterial(
615  model.materials_[mobj.material_idx]));
616  tmeshes.emplace(mobj.mesh_name, tmesh);
617  }
618  return tmeshes;
619 }
620 
621 TriangleMesh TriangleMesh::To(const core::Device &device, bool copy) const {
622  if (!copy && GetDevice() == device) {
623  return *this;
624  }
625  TriangleMesh mesh(device);
626  for (const auto &kv : triangle_attr_) {
627  mesh.SetTriangleAttr(kv.first, kv.second.To(device, /*copy=*/true));
628  }
629  for (const auto &kv : vertex_attr_) {
630  mesh.SetVertexAttr(kv.first, kv.second.To(device, /*copy=*/true));
631  }
632  return mesh;
633 }
634 
637  return pcd.ComputeConvexHull();
638 }
639 
641  const core::Tensor &normal) const {
642  using namespace vtkutils;
645  // allow int types for convenience
650 
651  auto point_ = point.To(core::Device(), core::Float64).Contiguous();
652  auto normal_ = normal.To(core::Device(), core::Float64).Contiguous();
653 
654  auto polydata = CreateVtkPolyDataFromGeometry(
655  *this, GetVertexAttr().GetKeySet(), GetTriangleAttr().GetKeySet(),
656  {}, {}, false);
657 
658  vtkNew<vtkPlane> clipPlane;
659  clipPlane->SetNormal(normal_.GetDataPtr<double>());
660  clipPlane->SetOrigin(point_.GetDataPtr<double>());
661  vtkNew<vtkClipPolyData> clipper;
662  clipper->SetInputData(polydata);
663  clipper->SetClipFunction(clipPlane);
664  vtkNew<vtkCleanPolyData> cleaner;
665  cleaner->SetInputConnection(clipper->GetOutputPort());
666  cleaner->Update();
667  auto clipped_polydata = cleaner->GetOutput();
668  return CreateTriangleMeshFromVtkPolyData(clipped_polydata);
669 }
670 
672  const core::Tensor &point,
673  const core::Tensor &normal,
674  const std::vector<double> contour_values) const {
675  using namespace vtkutils;
678  // allow int types for convenience
683 
684  auto point_ = point.To(core::Device(), core::Float64).Contiguous();
685  auto normal_ = normal.To(core::Device(), core::Float64).Contiguous();
686 
687  auto polydata = CreateVtkPolyDataFromGeometry(
688  *this, GetVertexAttr().GetKeySet(), {}, {}, {}, false);
689 
690  vtkNew<vtkPlane> clipPlane;
691  clipPlane->SetNormal(normal_.GetDataPtr<double>());
692  clipPlane->SetOrigin(point_.GetDataPtr<double>());
693 
694  vtkNew<vtkCutter> cutter;
695  cutter->SetInputData(polydata);
696  cutter->SetCutFunction(clipPlane);
697  cutter->GenerateTrianglesOff();
698  cutter->SetNumberOfContours(contour_values.size());
699  int i = 0;
700  for (double value : contour_values) {
701  cutter->SetValue(i++, value);
702  }
703  cutter->Update();
704  auto slices_polydata = cutter->GetOutput();
705 
706  return CreateLineSetFromVtkPolyData(slices_polydata);
707 }
708 
710  double target_reduction, bool preserve_volume) const {
711  using namespace vtkutils;
712  if (target_reduction >= 1.0 || target_reduction < 0) {
714  "target_reduction must be in the range [0,1) but is {}",
715  target_reduction);
716  }
717 
718  // exclude attributes because they will not be preserved
719  auto polydata = CreateVtkPolyDataFromGeometry(*this, {}, {}, {}, {}, false);
720 
721  vtkNew<vtkQuadricDecimation> decimate;
722  decimate->SetInputData(polydata);
723  decimate->SetTargetReduction(target_reduction);
724  decimate->SetVolumePreservation(preserve_volume);
725  decimate->Update();
726  auto decimated_polydata = decimate->GetOutput();
727 
728  return CreateTriangleMeshFromVtkPolyData(decimated_polydata);
729 }
730 
731 namespace {
732 TriangleMesh BooleanOperation(const TriangleMesh &mesh_A,
733  const TriangleMesh &mesh_B,
734  double tolerance,
735  int op) {
736  using namespace vtkutils;
737  // exclude triangle attributes because they will not be preserved
738  auto polydata_A = CreateVtkPolyDataFromGeometry(
739  mesh_A, mesh_A.GetVertexAttr().GetKeySet(), {}, {}, {}, false);
740  auto polydata_B = CreateVtkPolyDataFromGeometry(
741  mesh_B, mesh_B.GetVertexAttr().GetKeySet(), {}, {}, {}, false);
742 
743  // clean meshes before passing them to the boolean operation
744  vtkNew<vtkCleanPolyData> cleaner_A;
745  cleaner_A->SetInputData(polydata_A);
746 
747  vtkNew<vtkCleanPolyData> cleaner_B;
748  cleaner_B->SetInputData(polydata_B);
749 
750  vtkNew<vtkBooleanOperationPolyDataFilter> boolean_filter;
751  boolean_filter->SetOperation(op);
752  boolean_filter->SetTolerance(tolerance);
753  boolean_filter->SetInputConnection(0, cleaner_A->GetOutputPort());
754  boolean_filter->SetInputConnection(1, cleaner_B->GetOutputPort());
755  boolean_filter->Update();
756  auto out_polydata = boolean_filter->GetOutput();
757 
758  return CreateTriangleMeshFromVtkPolyData(out_polydata);
759 }
760 } // namespace
761 
763  double tolerance) const {
764  return BooleanOperation(*this, mesh, tolerance,
765  vtkBooleanOperationPolyDataFilter::VTK_UNION);
766 }
767 
769  double tolerance) const {
770  return BooleanOperation(
771  *this, mesh, tolerance,
772  vtkBooleanOperationPolyDataFilter::VTK_INTERSECTION);
773 }
774 
776  double tolerance) const {
777  return BooleanOperation(*this, mesh, tolerance,
778  vtkBooleanOperationPolyDataFilter::VTK_DIFFERENCE);
779 }
780 
783 }
784 
787 }
788 
789 TriangleMesh TriangleMesh::FillHoles(double hole_size) const {
790  using namespace vtkutils;
791  // do not include triangle attributes because they will not be preserved by
792  // the hole filling algorithm
793  auto polydata = CreateVtkPolyDataFromGeometry(
794  *this, GetVertexAttr().GetKeySet(), {}, {}, {}, false);
795  vtkNew<vtkFillHolesFilter> fill_holes;
796  fill_holes->SetInputData(polydata);
797  fill_holes->SetHoleSize(hole_size);
798  fill_holes->Update();
799  auto result = fill_holes->GetOutput();
801 }
802 
803 std::tuple<float, int, int> TriangleMesh::ComputeUVAtlas(
804  size_t size,
805  float gutter,
806  float max_stretch,
807  int parallel_partitions,
808  int nthreads) {
809  return kernel::uvunwrapping::ComputeUVAtlas(*this, size, size, gutter,
810  max_stretch,
811  parallel_partitions, nthreads);
812 }
813 
814 namespace {
830 template <class TAttr, class TInt, bool VERTEX_ATTR>
831 core::Tensor BakeAttribute(int size,
832  float margin,
833  const core::Tensor &attr,
834  const core::Tensor &triangle_indices,
835  const core::Tensor &primitive_ids,
836  const core::Tensor &primitive_uvs,
837  const core::Tensor &sqrdistance,
838  TAttr fill_value) {
839  core::SizeVector tex_shape({size, size});
840  tex_shape.insert(tex_shape.end(), attr.GetShapeRef().begin() + 1,
841  attr.GetShapeRef().end());
842  core::SizeVector components_shape(attr.GetShapeRef().begin() + 1,
843  attr.GetShapeRef().end());
844  const int num_components =
845  components_shape.NumElements(); // is 1 for empty shape
846  core::Tensor tex = core::Tensor::Empty(tex_shape, attr.GetDtype());
847 
848  const float threshold = (margin / size) * (margin / size);
849  Eigen::Map<const Eigen::MatrixXf> sqrdistance_map(
850  sqrdistance.GetDataPtr<float>(), size, size);
851  Eigen::Map<Eigen::Matrix<TAttr, Eigen::Dynamic, Eigen::Dynamic>> tex_map(
852  tex.GetDataPtr<TAttr>(), num_components, size * size);
853  Eigen::Map<const Eigen::Matrix<uint32_t, Eigen::Dynamic, Eigen::Dynamic>>
854  tid_map(primitive_ids.GetDataPtr<uint32_t>(), size, size);
855  Eigen::Map<const Eigen::MatrixXf> uv_map(primitive_uvs.GetDataPtr<float>(),
856  2, size * size);
857  Eigen::Map<const Eigen::Matrix<TAttr, Eigen::Dynamic, Eigen::Dynamic>>
858  attr_map(attr.GetDataPtr<TAttr>(), num_components,
859  attr.GetLength());
860  Eigen::Map<const Eigen::Matrix<TInt, 3, Eigen::Dynamic>>
861  triangle_indices_map(triangle_indices.GetDataPtr<TInt>(), 3,
862  triangle_indices.GetLength());
863 
864  for (int i = 0; i < size; ++i) {
865  for (int j = 0; j < size; ++j) {
866  const int64_t linear_idx = i * size + j;
867  if (sqrdistance_map(j, i) <= threshold) {
868  const uint32_t tid = tid_map(j, i);
869  if (VERTEX_ATTR) {
870  const auto &a = attr_map.col(triangle_indices_map(0, tid));
871  const auto &b = attr_map.col(triangle_indices_map(1, tid));
872  const auto &c = attr_map.col(triangle_indices_map(2, tid));
873  TAttr u = uv_map(0, linear_idx);
874  TAttr v = uv_map(1, linear_idx);
875  tex_map.col(linear_idx) =
876  std::max<TAttr>(0, 1 - u - v) * a + u * b + v * c;
877  } else {
878  tex_map.col(linear_idx) = attr_map.col(tid);
879  }
880  } else {
881  tex_map.col(linear_idx).setConstant(fill_value);
882  }
883  }
884  }
885 
886  return tex;
887 }
888 
899 void ComputePrimitiveInfoTexture(int size,
900  core::Tensor &primitive_ids,
901  core::Tensor &primitive_uvs,
902  core::Tensor &sqrdistance,
903  const core::Tensor &texture_uvs) {
904  const int64_t num_triangles = texture_uvs.GetLength();
905 
906  // Generate vertices for each triangle using (u,v,0) as position.
907  core::Tensor vertices({num_triangles * 3, 3}, core::Float32);
908  {
909  const float *uv_ptr = texture_uvs.GetDataPtr<float>();
910  float *v_ptr = vertices.GetDataPtr<float>();
911  for (int64_t i = 0; i < texture_uvs.GetLength(); ++i) {
912  for (int64_t j = 0; j < 3; ++j) {
913  v_ptr[i * 9 + j * 3 + 0] = uv_ptr[i * 6 + j * 2 + 0];
914  v_ptr[i * 9 + j * 3 + 1] = uv_ptr[i * 6 + j * 2 + 1];
915  v_ptr[i * 9 + j * 3 + 2] = 0;
916  }
917  }
918  }
919  core::Tensor triangle_indices =
920  core::Tensor::Empty({num_triangles, 3}, core::UInt32);
921  std::iota(triangle_indices.GetDataPtr<uint32_t>(),
922  triangle_indices.GetDataPtr<uint32_t>() +
923  triangle_indices.NumElements(),
924  0);
925 
926  RaycastingScene scene;
927  scene.AddTriangles(vertices, triangle_indices);
928 
929  core::Tensor query_points =
931  float *ptr = query_points.GetDataPtr<float>();
932  for (int i = 0; i < size; ++i) {
933  float v = 1 - (i + 0.5f) / size;
934  for (int j = 0; j < size; ++j) {
935  float u = (j + 0.5f) / size;
936  ptr[i * size * 3 + j * 3 + 0] = u;
937  ptr[i * size * 3 + j * 3 + 1] = v;
938  ptr[i * size * 3 + j * 3 + 2] = 0;
939  }
940  }
941 
942  auto ans = scene.ComputeClosestPoints(query_points);
943 
944  Eigen::Map<Eigen::MatrixXf> query_points_map(
945  query_points.GetDataPtr<float>(), 3, size * size);
946  Eigen::Map<Eigen::MatrixXf> closest_points_map(
947  ans["points"].GetDataPtr<float>(), 3, size * size);
948  sqrdistance = core::Tensor::Empty({size, size}, core::Float32);
949  Eigen::Map<Eigen::VectorXf> sqrdistance_map(sqrdistance.GetDataPtr<float>(),
950  size * size);
951  sqrdistance_map =
952  (closest_points_map - query_points_map).colwise().squaredNorm();
953  primitive_ids = ans["primitive_ids"];
954  primitive_uvs = ans["primitive_uvs"];
955 }
956 void UpdateMaterialTextures(
957  std::unordered_map<std::string, core::Tensor> &textures,
958  visualization::rendering::Material &material) {
959  for (auto &tex : textures) {
960  core::SizeVector element_shape(tex.second.GetShapeRef().begin() + 2,
961  tex.second.GetShapeRef().end());
962  core::SizeVector shape(tex.second.GetShapeRef().begin(),
963  tex.second.GetShapeRef().begin() + 2);
964  if (tex.second.NumDims() > 2) {
965  shape.push_back(element_shape.NumElements());
966  }
967 
968  core::Tensor img_data = tex.second.Reshape(shape);
969  material.SetTextureMap(tex.first, Image(img_data));
970  }
971 }
972 
973 } // namespace
974 std::unordered_map<std::string, core::Tensor>
976  int size,
977  const std::unordered_set<std::string> &vertex_attr,
978  double margin,
979  double fill,
980  bool update_material) {
981  if (!vertex_attr.size()) {
982  return std::unordered_map<std::string, core::Tensor>();
983  }
984  if (!triangle_attr_.Contains("texture_uvs")) {
985  utility::LogError("Cannot find triangle attribute 'texture_uvs'");
986  }
987 
988  core::Tensor texture_uvs =
989  triangle_attr_.at("texture_uvs").To(core::Device()).Contiguous();
990  core::AssertTensorShape(texture_uvs, {core::None, 3, 2});
991  core::AssertTensorDtype(texture_uvs, {core::Float32});
992 
993  core::Tensor vertices({GetTriangleIndices().GetLength() * 3, 3},
994  core::Float32);
995  {
996  float *uv_ptr = texture_uvs.GetDataPtr<float>();
997  float *v_ptr = vertices.GetDataPtr<float>();
998  for (int64_t i = 0; i < texture_uvs.GetLength(); ++i) {
999  for (int64_t j = 0; j < 3; ++j) {
1000  v_ptr[i * 9 + j * 3 + 0] = uv_ptr[i * 6 + j * 2 + 0];
1001  v_ptr[i * 9 + j * 3 + 1] = uv_ptr[i * 6 + j * 2 + 1];
1002  v_ptr[i * 9 + j * 3 + 2] = 0;
1003  }
1004  }
1005  }
1006  core::Tensor triangle_indices =
1008  std::iota(triangle_indices.GetDataPtr<uint32_t>(),
1009  triangle_indices.GetDataPtr<uint32_t>() +
1010  triangle_indices.NumElements(),
1011  0);
1012 
1013  core::Tensor primitive_ids, primitive_uvs, sqrdistance;
1014  ComputePrimitiveInfoTexture(size, primitive_ids, primitive_uvs, sqrdistance,
1015  texture_uvs);
1016 
1017  std::unordered_map<std::string, core::Tensor> result;
1018  for (auto attr : vertex_attr) {
1019  if (!vertex_attr_.Contains(attr)) {
1020  utility::LogError("Cannot find vertex attribute '{}'", attr);
1021  }
1022  core::Tensor tensor =
1023  vertex_attr_.at(attr).To(core::Device()).Contiguous();
1025  tensor.GetDtype(), GetTriangleIndices().GetDtype(), [&]() {
1026  core::Tensor tex = BakeAttribute<scalar_t, int_t, true>(
1027  size, margin, tensor, GetTriangleIndices(),
1028  primitive_ids, primitive_uvs, sqrdistance,
1029  scalar_t(fill));
1030  result[attr] = tex;
1031  });
1032  }
1033  if (update_material) {
1034  UpdateMaterialTextures(result, this->GetMaterial());
1035  }
1036 
1037  return result;
1038 }
1039 
1040 std::unordered_map<std::string, core::Tensor>
1042  int size,
1043  const std::unordered_set<std::string> &triangle_attr,
1044  double margin,
1045  double fill,
1046  bool update_material) {
1047  if (!triangle_attr.size()) {
1048  return std::unordered_map<std::string, core::Tensor>();
1049  }
1050  if (!triangle_attr_.Contains("texture_uvs")) {
1051  utility::LogError("Cannot find triangle attribute 'texture_uvs'");
1052  }
1053 
1054  core::Tensor texture_uvs =
1055  triangle_attr_.at("texture_uvs").To(core::Device()).Contiguous();
1056  core::AssertTensorShape(texture_uvs, {core::None, 3, 2});
1057  core::AssertTensorDtype(texture_uvs, {core::Float32});
1058 
1059  core::Tensor primitive_ids, primitive_uvs, sqrdistance;
1060  ComputePrimitiveInfoTexture(size, primitive_ids, primitive_uvs, sqrdistance,
1061  texture_uvs);
1062 
1063  std::unordered_map<std::string, core::Tensor> result;
1064  for (auto attr : triangle_attr) {
1065  if (!triangle_attr_.Contains(attr)) {
1066  utility::LogError("Cannot find triangle attribute '{}'", attr);
1067  }
1068  core::Tensor tensor =
1069  triangle_attr_.at(attr).To(core::Device()).Contiguous();
1071  core::Tensor tex;
1072  if (GetTriangleIndices().GetDtype() == core::Int32) {
1073  tex = BakeAttribute<scalar_t, int32_t, false>(
1074  size, margin, tensor, GetTriangleIndices(),
1075  primitive_ids, primitive_uvs, sqrdistance,
1076  scalar_t(fill));
1077  } else if (GetTriangleIndices().GetDtype() == core::Int64) {
1078  tex = BakeAttribute<scalar_t, int64_t, false>(
1079  size, margin, tensor, GetTriangleIndices(),
1080  primitive_ids, primitive_uvs, sqrdistance,
1081  scalar_t(fill));
1082  } else {
1083  utility::LogError("Unsupported triangle indices data type.");
1084  }
1085  result[attr] = tex;
1086  });
1087  }
1088  if (update_material) {
1089  UpdateMaterialTextures(result, this->GetMaterial());
1090  }
1091 
1092  return result;
1093 }
1094 
1096  const core::Tensor &axis,
1097  int resolution,
1098  double translation,
1099  bool capping) const {
1100  using namespace vtkutils;
1101  return ExtrudeRotationTriangleMesh(*this, angle, axis, resolution,
1102  translation, capping);
1103 }
1104 
1106  double scale,
1107  bool capping) const {
1108  using namespace vtkutils;
1109  return ExtrudeLinearTriangleMesh(*this, vector, scale, capping);
1110 }
1111 
1112 int TriangleMesh::PCAPartition(int max_faces) {
1113  core::Tensor verts = GetVertexPositions();
1115  if (!tris.GetLength()) {
1116  utility::LogError("Mesh must have at least one face.");
1117  }
1118  core::Tensor tris_centers = verts.IndexGet({tris}).Mean({1});
1119 
1120  int num_parititions;
1121  core::Tensor partition_ids;
1122  std::tie(num_parititions, partition_ids) =
1123  kernel::pcapartition::PCAPartition(tris_centers, max_faces);
1124  SetTriangleAttr("partition_ids", partition_ids.To(GetDevice()));
1125  return num_parititions;
1126 }
1127 
1131 template <typename T>
1133  const core::Tensor &vertex_mask) {
1134  int64_t num_verts = vertex_mask.GetLength();
1135  int64_t num_tris = tris_cpu.GetLength();
1136  const T *vertex_mask_ptr = vertex_mask.GetDataPtr<T>();
1137  std::vector<T> prefix_sum(num_verts + 1, 0);
1138  utility::InclusivePrefixSum(vertex_mask_ptr, vertex_mask_ptr + num_verts,
1139  &prefix_sum[1]);
1140 
1141  // update triangle indices
1142  T *vert_idx_ptr = tris_cpu.GetDataPtr<T>();
1143  for (int64_t i = 0; i < num_tris * 3; ++i) {
1144  vert_idx_ptr[i] = prefix_sum[vert_idx_ptr[i]];
1145  }
1146 }
1147 
1154  const TriangleMesh &src,
1155  const core::Tensor &vertex_mask,
1156  const core::Tensor &tri_mask) {
1157  if (src.HasVertexPositions() && dst.HasVertexPositions()) {
1158  for (auto item : src.GetVertexAttr()) {
1159  if (!dst.HasVertexAttr(item.first)) {
1160  dst.SetVertexAttr(item.first,
1161  item.second.IndexGet({vertex_mask}));
1162  }
1163  }
1164  }
1165 
1166  if (src.HasTriangleIndices() && dst.HasTriangleIndices()) {
1167  for (auto item : src.GetTriangleAttr()) {
1168  if (!dst.HasTriangleAttr(item.first)) {
1169  dst.SetTriangleAttr(item.first,
1170  item.second.IndexGet({tri_mask}));
1171  }
1172  }
1173  }
1174 }
1175 
1177  if (!HasVertexPositions()) {
1179  "[SelectFacesByMask] mesh has no vertex positions.");
1180  return {};
1181  }
1182  if (!HasTriangleIndices()) {
1184  "[SelectFacesByMask] mesh has no triangle indices.");
1185  return {};
1186  }
1187 
1192 
1193  // select triangles
1194  core::Tensor tris = GetTriangleIndices().IndexGet({mask});
1195  core::Tensor tris_cpu = tris.To(core::Device()).Contiguous();
1196 
1197  // create mask for vertices that are part of the selected faces
1198  const int64_t num_verts = GetVertexPositions().GetLength();
1199  // empty tensor to further construct the vertex mask
1200  core::Tensor vertex_mask;
1201 
1202  DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tris_cpu.GetDtype(), tris, [&]() {
1203  vertex_mask = core::Tensor::Zeros(
1204  {num_verts}, core::Dtype::FromType<scalar_tris_t>());
1205  const int64_t num_tris = tris_cpu.GetLength();
1206  scalar_tris_t *vertex_mask_ptr =
1207  vertex_mask.GetDataPtr<scalar_tris_t>();
1208  scalar_tris_t *vert_idx_ptr = tris_cpu.GetDataPtr<scalar_tris_t>();
1209  // mask for the vertices, which are used in the triangles
1210  for (int64_t i = 0; i < num_tris * 3; ++i) {
1211  vertex_mask_ptr[vert_idx_ptr[i]] = 1;
1212  }
1213  UpdateTriangleIndicesByVertexMask<scalar_tris_t>(tris_cpu, vertex_mask);
1214  });
1215 
1216  tris = tris_cpu.To(GetDevice());
1217  vertex_mask = vertex_mask.To(GetDevice(), core::Bool);
1218  core::Tensor verts = GetVertexPositions().IndexGet({vertex_mask});
1219  TriangleMesh result(verts, tris);
1220 
1221  CopyAttributesByMasks(result, *this, vertex_mask, mask);
1222 
1223  return result;
1224 }
1225 
1227 template <typename T,
1228  typename std::enable_if<std::is_integral<T>::value &&
1229  !std::is_same<T, bool>::value &&
1230  std::is_signed<T>::value,
1231  T>::type * = nullptr>
1232 static bool IsNegative(T val) {
1233  return val < 0;
1234 }
1235 
1238 template <typename T,
1239  typename std::enable_if<std::is_integral<T>::value &&
1240  !std::is_same<T, bool>::value &&
1241  !std::is_signed<T>::value,
1242  T>::type * = nullptr>
1243 static bool IsNegative(T val) {
1244  return false;
1245 }
1246 
1248  bool copy_attributes /*=true*/) const {
1249  core::AssertTensorShape(indices, {indices.GetLength()});
1250  if (indices.NumElements() == 0) {
1251  return {};
1252  }
1253  if (!HasVertexPositions()) {
1254  utility::LogWarning("[SelectByIndex] TriangleMesh has no vertices.");
1255  return {};
1256  }
1258 
1259  // we allow indices of an integral type only
1260  core::Dtype::DtypeCode indices_dtype_code =
1261  indices.GetDtype().GetDtypeCode();
1262  if (indices_dtype_code != core::Dtype::DtypeCode::Int &&
1263  indices_dtype_code != core::Dtype::DtypeCode::UInt) {
1265  "[SelectByIndex] indices are not of integral type {}.",
1266  indices.GetDtype().ToString());
1267  }
1268  core::Tensor indices_cpu = indices.To(core::Device()).Contiguous();
1269  core::Tensor tris_cpu, tri_mask;
1270  core::Dtype tri_dtype;
1271  if (HasTriangleIndices()) {
1273  tris_cpu = GetTriangleIndices().To(core::Device()).Contiguous();
1274  // bool mask for triangles.
1275  tri_mask = core::Tensor::Zeros({tris_cpu.GetLength()}, core::Bool);
1276  tri_dtype = tris_cpu.GetDtype();
1277  } else {
1278  utility::LogWarning("TriangleMesh has no triangle indices.");
1279  tri_dtype = core::Int64;
1280  }
1281 
1282  // int mask to select vertices for the new mesh. We need it as int as we
1283  // will use its values to sum up and get the map of new indices
1284  core::Tensor vertex_mask =
1286 
1287  DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() {
1289  indices_cpu.GetDtype(), indices, [&]() {
1290  const int64_t num_tris = tris_cpu.GetLength();
1291  const int64_t num_verts = vertex_mask.GetLength();
1292 
1293  // compute the vertices mask
1294  scalar_tris_t *vertex_mask_ptr =
1295  vertex_mask.GetDataPtr<scalar_tris_t>();
1296  const scalar_indices_t *indices_ptr =
1297  indices_cpu.GetDataPtr<scalar_indices_t>();
1298  for (int64_t i = 0; i < indices.GetLength(); ++i) {
1299  if (IsNegative(indices_ptr[i]) ||
1300  indices_ptr[i] >=
1301  static_cast<scalar_indices_t>(num_verts)) {
1302  utility::LogWarning(
1303  "[SelectByIndex] indices contains index {} "
1304  "out of range. "
1305  "It is ignored.",
1306  indices_ptr[i]);
1307  continue;
1308  }
1309  vertex_mask_ptr[indices_ptr[i]] = 1;
1310  }
1311 
1312  if (tri_mask.GetDtype() == core::Undefined) {
1313  // we don't need to compute triangles, if there are none
1314  return;
1315  }
1316 
1317  // Build the triangle mask
1318  scalar_tris_t *tris_cpu_ptr =
1319  tris_cpu.GetDataPtr<scalar_tris_t>();
1320  bool *tri_mask_ptr = tri_mask.GetDataPtr<bool>();
1321  for (int64_t i = 0; i < num_tris; ++i) {
1322  if (vertex_mask_ptr[tris_cpu_ptr[3 * i]] == 1 &&
1323  vertex_mask_ptr[tris_cpu_ptr[3 * i + 1]] == 1 &&
1324  vertex_mask_ptr[tris_cpu_ptr[3 * i + 2]] == 1) {
1325  tri_mask_ptr[i] = true;
1326  }
1327  }
1328  // select only needed triangles
1329  tris_cpu = tris_cpu.IndexGet({tri_mask});
1330  // update the triangle indices
1331  UpdateTriangleIndicesByVertexMask<scalar_tris_t>(
1332  tris_cpu, vertex_mask);
1333  });
1334  });
1335 
1336  // send the vertex mask and triangle mask to original device and apply to
1337  // vertices
1338  vertex_mask = vertex_mask.To(GetDevice(), core::Bool);
1339  if (tri_mask.NumElements() > 0) { // To() needs non-empty tensor
1340  tri_mask = tri_mask.To(GetDevice());
1341  }
1342  core::Tensor new_vertices = GetVertexPositions().IndexGet({vertex_mask});
1344  result.SetVertexPositions(new_vertices);
1345  if (tris_cpu.NumElements() > 0) { // To() needs non-empty tensor
1346  result.SetTriangleIndices(tris_cpu.To(GetDevice()));
1347  }
1348  if (copy_attributes)
1349  CopyAttributesByMasks(result, *this, vertex_mask, tri_mask);
1350 
1351  return result;
1352 }
1353 
1355  if (!HasVertexPositions() || GetVertexPositions().GetLength() == 0) {
1357  "[RemoveUnreferencedVertices] TriangleMesh has no vertices.");
1358  return *this;
1359  }
1361 
1362  core::Dtype tri_dtype = HasTriangleIndices()
1364  : core::Int64;
1365 
1366  int64_t num_verts_old = GetVertexPositions().GetLength();
1367  // int mask for vertices as we need to remap indices.
1368  core::Tensor vertex_mask = core::Tensor::Zeros({num_verts_old}, tri_dtype);
1369 
1370  if (!HasTriangleIndices() || GetTriangleIndices().GetLength() == 0) {
1372  "[RemoveUnreferencedVertices] TriangleMesh has no triangles. "
1373  "Removing all vertices.");
1374  // in this case we need to empty vertices and their attributes
1375  } else {
1377  core::Tensor tris_cpu =
1379  DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() {
1380  scalar_tris_t *tris_ptr = tris_cpu.GetDataPtr<scalar_tris_t>();
1381  scalar_tris_t *vertex_mask_ptr =
1382  vertex_mask.GetDataPtr<scalar_tris_t>();
1383  for (int i = 0; i < tris_cpu.GetLength(); i++) {
1384  vertex_mask_ptr[tris_ptr[3 * i]] = 1;
1385  vertex_mask_ptr[tris_ptr[3 * i + 1]] = 1;
1386  vertex_mask_ptr[tris_ptr[3 * i + 2]] = 1;
1387  }
1388 
1389  UpdateTriangleIndicesByVertexMask<scalar_tris_t>(tris_cpu,
1390  vertex_mask);
1391  });
1392  SetTriangleIndices(tris_cpu.To(GetDevice()));
1393  }
1394 
1395  // send the vertex mask to original device and apply to
1396  // vertices
1397  vertex_mask = vertex_mask.To(GetDevice(), core::Bool);
1398  for (auto item : GetVertexAttr()) {
1399  SetVertexAttr(item.first, item.second.IndexGet({vertex_mask}));
1400  }
1401 
1403  "[RemoveUnreferencedVertices] {:d} vertices have been removed.",
1404  static_cast<int>(num_verts_old - GetVertexPositions().GetLength()));
1405 
1406  return *this;
1407 }
1408 
1409 namespace {
1410 
1411 core::Tensor Project(const core::Tensor &t_xyz, // contiguous {...,3}
1412  const core::Tensor &t_intrinsic_matrix, // {3,3}
1413  const core::Tensor &t_extrinsic_matrix, // {4,4}
1414  core::Tensor &t_xy) { // contiguous {...,2}
1415  auto xy_shape = t_xyz.GetShape();
1416  auto dt = t_xyz.GetDtype();
1417  auto t_K = t_intrinsic_matrix.To(dt).Contiguous(),
1418  t_T = t_extrinsic_matrix.To(dt).Contiguous();
1419  xy_shape[t_xyz.NumDims() - 1] = 2;
1420  if (t_xy.GetDtype() != dt || t_xy.GetShape() != xy_shape) {
1421  t_xy = core::Tensor(xy_shape, dt);
1422  }
1424  // Eigen is column major
1425  Eigen::Map<Eigen::MatrixX<scalar_t>> xy(t_xy.GetDataPtr<scalar_t>(), 2,
1426  t_xy.NumElements() / 2);
1427  Eigen::Map<const Eigen::MatrixX<scalar_t>> xyz(
1428  t_xyz.GetDataPtr<scalar_t>(), 3, t_xyz.NumElements() / 3);
1429  Eigen::Map<const Eigen::Matrix3<scalar_t>> KT(
1430  t_K.GetDataPtr<scalar_t>());
1431  Eigen::Map<const Eigen::Matrix4<scalar_t>> TT(
1432  t_T.GetDataPtr<scalar_t>());
1433 
1434  auto K = KT.transpose();
1435  auto T = TT.transpose();
1436  auto R = T.topLeftCorner<3, 3>();
1437  auto t = T.topRightCorner<3, 1>();
1438  auto pxyz = (K * ((R * xyz).colwise() + t)).array();
1439  xy = pxyz.topRows<2>().rowwise() / pxyz.bottomRows<1>();
1440  });
1441  return t_xy; // contiguous {...,2}
1442 }
1443 
1445 float get_min_cam_sqrdistance(
1446  const core::Tensor &positions,
1447  const std::vector<core::Tensor> &extrinsic_matrices) {
1448  const size_t MAXPTS = 10000;
1449  core::Tensor cam_loc({int64_t(extrinsic_matrices.size()), 3},
1450  core::Float32);
1451  for (size_t k = 0; k < extrinsic_matrices.size(); ++k) {
1452  const core::Tensor RT = extrinsic_matrices[k].Slice(0, 0, 3);
1453  cam_loc[k] =
1454  -RT.Slice(1, 0, 3).T().Matmul(RT.Slice(1, 3, 4)).Reshape({-1});
1455  }
1456  size_t npts = positions.GetShape(0);
1457  const core::Tensor pos_sample =
1458  npts > MAXPTS ? positions.Slice(0, 0, -1, npts / MAXPTS)
1459  : positions;
1460  auto nns = core::nns::NearestNeighborSearch(pos_sample);
1461  nns.KnnIndex();
1462  float min_sqrdistance = nns.KnnSearch(cam_loc, 1)
1463  .second.Min({0, 1})
1464  .To(core::Device(), core::Float32)
1465  .Item<float>();
1466  return min_sqrdistance;
1467 }
1468 
1469 } // namespace
1470 
1472  const std::vector<Image> &images,
1473  const std::vector<core::Tensor> &intrinsic_matrices,
1474  const std::vector<core::Tensor> &extrinsic_matrices,
1475  int tex_size /*=1024*/,
1476  bool update_material /*=true*/) {
1477  if (!GetDevice().IsCPU() || !Image::HAVE_IPP) {
1479  "ProjectImagesToAlbedo is only supported on x86_64 CPU "
1480  "devices.");
1481  }
1482  using core::None;
1483  using tk = core::TensorKey;
1484  constexpr float EPS = 1e-6;
1485  if (!HasTriangleAttr("texture_uvs")) {
1487  "TriangleMesh does not contain 'texture_uvs'. Please compute "
1488  "it with ComputeUVAtlas() first.");
1489  }
1490  core::Tensor texture_uvs =
1491  GetTriangleAttr("texture_uvs").To(core::Device()).Contiguous();
1492  core::AssertTensorShape(texture_uvs, {core::None, 3, 2});
1493  core::AssertTensorDtype(texture_uvs, {core::Float32});
1494 
1495  if (images.size() != extrinsic_matrices.size() ||
1496  images.size() != intrinsic_matrices.size()) {
1498  "Received {} images, but {} extrinsic matrices and {} "
1499  "intrinsic matrices.",
1500  images.size(), extrinsic_matrices.size(),
1501  intrinsic_matrices.size());
1502  }
1503 
1504  // softmax_shift is used to prevent overflow in the softmax function.
1505  // softmax_shift is set so that max value of weighting function is exp(64),
1506  // well within float range. (exp(89.f) is inf)
1507  float min_sqr_distance =
1508  get_min_cam_sqrdistance(GetVertexPositions(), extrinsic_matrices);
1509  const float softmax_scale = 20 * min_sqr_distance;
1510  constexpr float softmax_shift = 10.f, LOG_FLT_MAX = 88.f;
1511  // (u,v) -> (x,y,z) : {tex_size, tex_size, 3}
1512  // margin \propto tex_size (default 2px for 512 size texture as in
1513  // BakeVertexAttrTextures()). Should be at least 1px.
1514  core::Tensor position_map = BakeVertexAttrTextures(
1515  tex_size, {"positions"}, /*margin=*/(tex_size + 255) / 256,
1516  /*fill=*/0,
1517  /*update_material=*/false)["positions"];
1518  core::Tensor albedo =
1519  core::Tensor::Zeros({tex_size, tex_size, 4}, core::Float32);
1520  albedo.Slice(2, 3, 4).Fill(EPS); // regularize weight
1521  std::mutex albedo_mutex;
1522 
1523  RaycastingScene rcs;
1524  rcs.AddTriangles(*this);
1525 
1526  // setup working data for each task.
1527  size_t max_workers = tbb::this_task_arena::max_concurrency();
1528  // Tensor copy ctor does shallow copies - OK for empty tensors.
1529  std::vector<core::Tensor> this_albedo(max_workers,
1531  weighted_image(max_workers, core::Tensor({}, core::Float32)),
1532  uv2xy(max_workers, core::Tensor({}, core::Float32)),
1533  uvrays(max_workers, core::Tensor({}, core::Float32));
1534 
1535  auto project_one_image = [&](size_t i) {
1536  size_t widx = tbb::this_task_arena::current_thread_index();
1537  // initialize task variables
1538  if (!this_albedo[widx].GetShape().IsCompatible(
1539  {tex_size, tex_size, 4})) {
1540  this_albedo[widx] =
1541  core::Tensor::Empty({tex_size, tex_size, 4}, core::Float32);
1542  uvrays[widx] =
1543  core::Tensor::Empty({tex_size, tex_size, 6}, core::Float32);
1544  }
1545  auto width = images[i].GetCols(), height = images[i].GetRows();
1546  if (!weighted_image[widx].GetShape().IsCompatible({height, width, 4})) {
1547  weighted_image[widx] =
1549  }
1550  core::AssertTensorShape(intrinsic_matrices[i], {3, 3});
1551  core::AssertTensorShape(extrinsic_matrices[i], {4, 4});
1552 
1553  // A. Get image space weight matrix, as inverse of pixel
1554  // footprint on the mesh.
1556  intrinsic_matrices[i], extrinsic_matrices[i], width, height);
1557  core::Tensor cam_loc =
1558  rays.GetItem({tk::Index(0), tk::Index(0), tk::Slice(0, 3, 1)});
1559 
1560  // A nested parallel_for's threads must be isolated from the threads
1561  // running this paralel_for, else we get BAD ACCESS errors.
1562  auto result = tbb::this_task_arena::isolate(
1563  [&rays, &rcs]() { return rcs.CastRays(rays); });
1564  // Eigen is column-major order
1565  Eigen::Map<Eigen::ArrayXXf> normals_e(
1566  result["primitive_normals"].GetDataPtr<float>(), 3,
1567  width * height);
1568  Eigen::Map<Eigen::ArrayXXf> rays_e(rays.GetDataPtr<float>(), 6,
1569  width * height);
1570  Eigen::Map<Eigen::ArrayXXf> t_hit(result["t_hit"].GetDataPtr<float>(),
1571  1, width * height);
1572  auto depth = t_hit * rays_e.bottomRows<3>().colwise().norm().array();
1573  // removing this eval() increase runtime a lot (?)
1574  auto rays_dir = rays_e.bottomRows<3>().colwise().normalized().eval();
1575  auto pixel_foreshortening = (normals_e * rays_dir)
1576  .colwise()
1577  .sum()
1578  .abs(); // ignore face orientation
1579  // fix for bad normals
1580  auto inv_footprint =
1581  pixel_foreshortening.isNaN().select(0, pixel_foreshortening) /
1582  (depth * depth);
1584  "[ProjectImagesToAlbedo] Image {}, weight (inv_footprint) "
1585  "range: {}-{}",
1586  i, inv_footprint.minCoeff(), inv_footprint.maxCoeff());
1587  weighted_image[widx].Slice(2, 0, 3) =
1588  images[i].To(core::Float32).AsTensor(); // range: [0,1]
1589  Eigen::Map<Eigen::MatrixXf> weighted_image_e(
1590  weighted_image[widx].GetDataPtr<float>(), 4, width * height);
1591  weighted_image_e.bottomRows<1>() = inv_footprint;
1592 
1593  // B. Get texture space (u,v) -> (x,y) map and valid domain in
1594  // uv space.
1595  uvrays[widx].GetItem({tk::Slice(0, None, 1), tk::Slice(0, None, 1),
1596  tk::Slice(0, 3, 1)}) = cam_loc;
1597  uvrays[widx].GetItem({tk::Slice(0, None, 1), tk::Slice(0, None, 1),
1598  tk::Slice(3, 6, 1)}) = position_map - cam_loc;
1599  // A nested parallel_for's threads must be isolated from the threads
1600  // running this paralel_for, else we get BAD ACCESS errors.
1601  result = tbb::this_task_arena::isolate(
1602  [&rcs, &uvrays, widx]() { return rcs.CastRays(uvrays[widx]); });
1603  auto &t_hit_uv = result["t_hit"];
1604 
1605  Project(position_map, intrinsic_matrices[i], extrinsic_matrices[i],
1606  uv2xy[widx]); // {ts, ts, 2}
1607  // Disable self-occluded points
1608  for (float *p_uv2xy = uv2xy[widx].GetDataPtr<float>(),
1609  *p_t_hit = t_hit_uv.GetDataPtr<float>();
1610  p_uv2xy <
1611  uv2xy[widx].GetDataPtr<float>() + uv2xy[widx].NumElements();
1612  p_uv2xy += 2, ++p_t_hit) {
1613  if (*p_t_hit < 1 - EPS) *p_uv2xy = *(p_uv2xy + 1) = -1.f;
1614  }
1615  core::Tensor uv2xy2 =
1616  uv2xy[widx].Permute({2, 0, 1}).Contiguous(); // {2, ts, ts}
1617 
1618  // C. Interpolate weighted image to weighted texture
1619  // albedo[u,v] = image[ i[u,v], j[u,v] ]
1620  this_albedo[widx].Fill(0.f);
1621  IPP_CALL(ipp::Remap, weighted_image[widx], /*{height, width, 4} f32*/
1622  uv2xy2[0], /* {texsz, texsz} f32*/
1623  uv2xy2[1], /* {texsz, texsz} f32*/
1624  this_albedo[widx], /*{texsz, texsz, 4} f32*/
1626  // Weights can become negative with higher order interpolation
1627 
1628  std::unique_lock<std::mutex> albedo_lock{albedo_mutex};
1629  // ^^^ released when lambda returns.
1630  for (auto p_albedo = albedo.GetDataPtr<float>(),
1631  p_this_albedo = this_albedo[widx].GetDataPtr<float>();
1632  p_albedo < albedo.GetDataPtr<float>() + albedo.NumElements();
1633  p_albedo += 4, p_this_albedo += 4) {
1634  float softmax_weight =
1635  exp(std::min(LOG_FLT_MAX, softmax_scale * p_this_albedo[3] -
1636  softmax_shift));
1637  for (auto k = 0; k < 3; ++k)
1638  p_albedo[k] += p_this_albedo[k] * softmax_weight;
1639  p_albedo[3] += softmax_weight;
1640  }
1641  };
1642 
1643  std::vector<size_t> range(images.size(), 0);
1644  std::iota(range.begin(), range.end(), 0);
1645  tbb::parallel_for_each(range, project_one_image);
1646  albedo.Slice(2, 0, 3) /= albedo.Slice(2, 3, 4);
1647 
1648  // Image::To uses saturate_cast
1649  Image albedo_texture =
1650  Image(albedo.Slice(2, 0, 3).Contiguous())
1651  .To(core::UInt8, /*copy=*/true, /*scale=*/255.f);
1652  if (update_material) {
1653  if (!HasMaterial()) {
1655  GetMaterial().SetDefaultProperties(); // defaultUnlit
1656  }
1657  GetMaterial().SetAlbedoMap(albedo_texture);
1658  }
1659  return albedo_texture;
1660 }
1661 
1662 namespace {
1663 template <typename T,
1664  typename std::enable_if<std::is_integral<T>::value &&
1665  !std::is_same<T, bool>::value,
1666  T>::type * = nullptr>
1667 using Edge = std::tuple<T, T>;
1668 }
1669 
1671 template <typename T>
1672 static inline Edge<T> GetOrderedEdge(T vidx0, T vidx1) {
1673  return (vidx0 < vidx1) ? Edge<T>{vidx0, vidx1} : Edge<T>{vidx1, vidx0};
1674 }
1675 
1676 template <typename T>
1677 static std::unordered_map<Edge<T>,
1678  std::vector<size_t>,
1681  std::unordered_map<Edge<T>, std::vector<size_t>,
1683  tris_per_edge;
1684  auto AddEdge = [&](T vidx0, T vidx1, int64_t tidx) {
1685  tris_per_edge[GetOrderedEdge(vidx0, vidx1)].push_back(tidx);
1686  };
1687  const T *tris_ptr = tris_cpu.GetDataPtr<T>();
1688  for (int64_t tidx = 0; tidx < tris_cpu.GetLength(); ++tidx) {
1689  const T *triangle = &tris_ptr[3 * tidx];
1690  AddEdge(triangle[0], triangle[1], tidx);
1691  AddEdge(triangle[1], triangle[2], tidx);
1692  AddEdge(triangle[2], triangle[0], tidx);
1693  }
1694  return tris_per_edge;
1695 }
1696 
1698  if (!HasVertexPositions() || GetVertexPositions().GetLength() == 0) {
1700  "[RemoveNonManifildEdges] TriangleMesh has no vertices.");
1701  return *this;
1702  }
1703 
1704  if (!HasTriangleIndices() || GetTriangleIndices().GetLength() == 0) {
1706  "[RemoveNonManifoldEdges] TriangleMesh has no triangles.");
1707  return *this;
1708  }
1709 
1712 
1713  core::Tensor tris_cpu =
1715 
1716  if (!HasTriangleAttr("areas")) {
1718  }
1719  core::Tensor tri_areas_cpu =
1720  GetTriangleAttr("areas").To(core::Device()).Contiguous();
1721 
1723  GetVertexPositions().GetDtype(), tris_cpu.GetDtype(), [&]() {
1724  scalar_t *tri_areas_ptr = tri_areas_cpu.GetDataPtr<scalar_t>();
1725  auto edges_to_tris = GetEdgeToTrianglesMap<int_t>(tris_cpu);
1726 
1727  // lambda to compare triangles areas by index
1728  auto area_greater_compare = [&tri_areas_ptr](size_t lhs,
1729  size_t rhs) {
1730  return tri_areas_ptr[lhs] > tri_areas_ptr[rhs];
1731  };
1732 
1733  // go through all edges and for those that have more than 2
1734  // triangles attached, remove the triangles with the minimal
1735  // area
1736  for (auto &kv : edges_to_tris) {
1737  // remove all triangles which are already marked for removal
1738  // (area < 0) note, the erasing of triangles happens
1739  // afterwards
1740  auto tris_end = std::remove_if(
1741  kv.second.begin(), kv.second.end(),
1742  [=](size_t t) { return tri_areas_ptr[t] < 0; });
1743  // count non-removed triangles (with area > 0).
1744  int n_tris = std::distance(kv.second.begin(), tris_end);
1745 
1746  if (n_tris <= 2) {
1747  // nothing to do here as either:
1748  // - all triangles of the edge are already marked for
1749  // deletion
1750  // - the edge is manifold: it has 1 or 2 triangles with
1751  // a non-negative area
1752  continue;
1753  }
1754 
1755  // now erase all triangle indices already marked for removal
1756  kv.second.erase(tris_end, kv.second.end());
1757 
1758  // find first to triangles with the maximal area
1759  std::nth_element(kv.second.begin(), kv.second.begin() + 1,
1760  kv.second.end(), area_greater_compare);
1761 
1762  // mark others for deletion
1763  for (auto it = kv.second.begin() + 2; it < kv.second.end();
1764  ++it) {
1765  tri_areas_ptr[*it] = -1;
1766  }
1767  }
1768  });
1769 
1770  // mask for triangles with positive area
1771  core::Tensor tri_mask = tri_areas_cpu.Gt(0.0).To(GetDevice());
1772 
1773  // pick up positive-area triangles (and their attributes)
1774  for (auto item : GetTriangleAttr()) {
1775  SetTriangleAttr(item.first, item.second.IndexGet({tri_mask}));
1776  }
1777 
1778  return *this;
1779 }
1780 
1782  bool allow_boundary_edges /* = true */) const {
1783  if (!HasVertexPositions()) {
1785  "[GetNonManifoldEdges] TriangleMesh has no vertices.");
1786  return {};
1787  }
1788 
1789  if (!HasTriangleIndices()) {
1791  "[GetNonManifoldEdges] TriangleMesh has no triangles.");
1792  return {};
1793  }
1794 
1796  core::Tensor tris_cpu =
1798  core::Dtype tri_dtype = tris_cpu.GetDtype();
1799 
1800  DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() {
1801  auto edges = GetEdgeToTrianglesMap<scalar_tris_t>(tris_cpu);
1802  std::vector<scalar_tris_t> non_manifold_edges;
1803 
1804  for (auto &kv : edges) {
1805  if ((allow_boundary_edges &&
1806  (kv.second.size() < 1 || kv.second.size() > 2)) ||
1807  (!allow_boundary_edges && kv.second.size() != 2)) {
1808  non_manifold_edges.push_back(std::get<0>(kv.first));
1809  non_manifold_edges.push_back(std::get<1>(kv.first));
1810  }
1811  }
1812 
1813  result = core::Tensor(non_manifold_edges,
1814  {(long int)non_manifold_edges.size() / 2, 2},
1815  tri_dtype, GetTriangleIndices().GetDevice());
1816  });
1817 
1818  return result;
1819 }
1820 
1822  size_t number_of_points, bool use_triangle_normal /*=false*/) {
1823  if (number_of_points <= 0) {
1824  utility::LogError("number_of_points <= 0");
1825  }
1826  if (IsEmpty()) {
1827  utility::LogError("Input mesh is empty. Cannot sample points.");
1828  }
1829  if (!HasTriangleIndices()) {
1830  utility::LogError("Input mesh has no triangles. Cannot sample points.");
1831  }
1832  if (use_triangle_normal && !HasTriangleNormals()) {
1833  ComputeTriangleNormals(true);
1834  }
1835  if (!HasTriangleAttr("areas")) {
1836  ComputeTriangleAreas(); // Compute area of each triangle
1837  }
1838  if (!IsCPU()) {
1840  "SamplePointsUniformly is implemented only on CPU. Computing "
1841  "on CPU.");
1842  }
1843  bool use_vert_normals = HasVertexNormals() && !use_triangle_normal;
1844  bool use_albedo =
1845  HasTriangleAttr("texture_uvs") && GetMaterial().HasAlbedoMap();
1846  bool use_vert_colors = HasVertexColors() && !use_albedo;
1847 
1848  auto cpu = core::Device();
1849  core::Tensor null_tensor({0}, core::Float32); // zero size tensor
1850  auto triangles = GetTriangleIndices().To(cpu).Contiguous(),
1851  vertices = GetVertexPositions().To(cpu).Contiguous();
1852  auto float_dt = vertices.GetDtype();
1853  auto areas = GetTriangleAttr("areas").To(cpu, float_dt).Contiguous(),
1854  vertex_normals =
1855  use_vert_normals
1856  ? GetVertexNormals().To(cpu, float_dt).Contiguous()
1857  : null_tensor,
1858  triangle_normals =
1859  use_triangle_normal
1860  ? GetTriangleNormals().To(cpu, float_dt).Contiguous()
1861  : null_tensor,
1862  vertex_colors =
1863  use_vert_colors
1865  : null_tensor,
1866  texture_uvs = use_albedo ? GetTriangleAttr("texture_uvs")
1867  .To(cpu, float_dt)
1868  .Contiguous()
1869  : null_tensor,
1870  // With correct range conversion [0,255] -> [0,1]
1871  albedo = use_albedo ? GetMaterial()
1872  .GetAlbedoMap()
1873  .To(core::Float32)
1874  .To(cpu)
1875  .AsTensor()
1876  : null_tensor;
1877  if (use_vert_colors) {
1878  if (GetVertexColors().GetDtype() == core::UInt8) vertex_colors /= 255;
1879  if (GetVertexColors().GetDtype() == core::UInt16)
1880  vertex_colors /= 65535;
1881  }
1882 
1883  std::array<core::Tensor, 3> result =
1885  triangles, vertices, areas, vertex_normals, vertex_colors,
1886  triangle_normals, texture_uvs, albedo, number_of_points);
1887 
1888  PointCloud pcd(result[0]);
1889  if (use_vert_normals || use_triangle_normal) pcd.SetPointNormals(result[1]);
1890  if (use_albedo || use_vert_colors) pcd.SetPointColors(result[2]);
1891  return pcd.To(GetDevice());
1892 }
1893 
1895  std::vector<Metric> metrics,
1896  MetricParameters params) const {
1897  if (IsEmpty() || mesh2.IsEmpty()) {
1898  utility::LogError("One or both input triangle meshes are empty!");
1899  }
1900  if (!IsCPU() || !mesh2.IsCPU()) {
1902  "ComputeDistance is implemented only on CPU. Computing on "
1903  "CPU.");
1904  }
1905  auto cpu_mesh1 = To(core::Device("CPU:0")),
1906  cpu_mesh2 = mesh2.To(core::Device("CPU:0"));
1907  core::Tensor points1 =
1908  cpu_mesh1.SamplePointsUniformly(params.n_sampled_points)
1909  .GetPointPositions();
1910  core::Tensor points2 =
1911  cpu_mesh2.SamplePointsUniformly(params.n_sampled_points)
1912  .GetPointPositions();
1913 
1914  RaycastingScene scene1, scene2;
1915  scene1.AddTriangles(cpu_mesh1);
1916  scene2.AddTriangles(cpu_mesh2);
1917 
1918  core::Tensor distance21 = scene1.ComputeDistance(points2);
1919  core::Tensor distance12 = scene2.ComputeDistance(points1);
1920 
1921  return ComputeMetricsCommon(distance12, distance21, metrics, params);
1922 }
1923 
1924 } // namespace geometry
1925 } // namespace t
1926 } // namespace cloudViewer
Common CUDA utilities.
#define CUDA_CALL(cuda_function,...)
Definition: CUDAUtils.h:49
#define DISPATCH_DTYPE_TO_TEMPLATE_WITH_BOOL(DTYPE,...)
Definition: Dispatch.h:68
#define DISPATCH_FLOAT_DTYPE_TO_TEMPLATE(DTYPE,...)
Definition: Dispatch.h:78
#define DISPATCH_FLOAT_INT_DTYPE_TO_TEMPLATE(FDTYPE, IDTYPE,...)
Definition: Dispatch.h:91
#define DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(DTYPE, PREFIX,...)
Definition: Dispatch.h:118
filament::Texture::InternalFormat format
double normal[3]
int width
long vertex_num
int size
int height
char type
#define IPP_CALL(ipp_function,...)
Definition: IPPImage.h:96
cmdLineReadable * params[]
#define AssertTensorDevice(tensor,...)
Definition: TensorCheck.h:45
#define AssertTensorDtype(tensor,...)
Definition: TensorCheck.h:21
#define AssertTensorDtypes(tensor,...)
Definition: TensorCheck.h:33
#define AssertTensorShape(tensor,...)
Definition: TensorCheck.h:61
core::Tensor result
Definition: VtkUtils.cpp:76
bool copy
Definition: VtkUtils.cpp:74
Triangular mesh.
Definition: ecvMesh.h:35
void addEigenVertices(const std::vector< Eigen::Vector3d > &vertices)
cloudViewer::VerticesIndexes * getTriangleVertIndexes(unsigned triangleIndex) override
Returns the indexes of the vertices of a given triangle.
std::vector< Eigen::Vector2d > triangle_uvs_
List of uv coordinates per triangle.
Definition: ecvMesh.h:625
std::vector< Eigen::Vector3d > getVertexColors() const
void addVertexColors(const std::vector< Eigen::Vector3d > &colors)
std::vector< Eigen::Vector3d > getEigenVertices() const
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
bool HasVertices() const
Definition: ecvMesh.h:208
std::vector< Eigen::Vector3d > getVertexNormals() const
virtual unsigned size() const override
Returns the number of triangles.
void addVertexNormals(const std::vector< Eigen::Vector3d > &normals)
std::vector< std::pair< std::string, Material > > materials_
Definition: ecvMesh.h:704
bool hasTriangleUvs() const
Definition: ecvMesh.h:717
std::string ToString() const
Returns string representation of device, e.g. "CPU:0", "CUDA:0".
Definition: Device.cpp:89
std::string ToString() const
Definition: Dtype.h:65
DtypeCode GetDtypeCode() const
Definition: Dtype.h:61
bool IsCUDA() const
Definition: Device.h:99
bool IsCPU() const
Definition: Device.h:95
iterator insert(iterator I, T &&Elt)
Definition: SmallVector.h:853
TensorKey is used to represent single index, slice or advanced indexing on a Tensor.
Definition: TensorKey.h:26
Tensor Contiguous() const
Definition: Tensor.cpp:772
Tensor Matmul(const Tensor &rhs) const
Definition: Tensor.cpp:1919
int64_t NumDims() const
Definition: Tensor.h:1172
Tensor Sum(const SizeVector &dims, bool keepdim=false) const
Definition: Tensor.cpp:1240
Tensor Gt(const Tensor &value) const
Element-wise greater-than of tensors, returning a new boolean tensor.
Definition: Tensor.cpp:1554
int64_t GetLength() const
Definition: Tensor.h:1125
Dtype GetDtype() const
Definition: Tensor.h:1164
Tensor GetItem(const TensorKey &tk) const
Definition: Tensor.cpp:473
Tensor Sub_(const Tensor &value)
Definition: Tensor.cpp:1153
Tensor Permute(const SizeVector &dims) const
Permute (dimension shuffle) the Tensor, returns a view.
Definition: Tensor.cpp:1028
Tensor IndexGet(const std::vector< Tensor > &index_tensors) const
Advanced indexing getter. This will always allocate a new Tensor.
Definition: Tensor.cpp:905
int64_t NumElements() const
Definition: Tensor.h:1170
Tensor Mul_(const Tensor &value)
Definition: Tensor.cpp:1189
static Tensor Zeros(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor fill with zeros.
Definition: Tensor.cpp:406
Tensor Add_(const Tensor &value)
Definition: Tensor.cpp:1117
Device GetDevice() const override
Definition: Tensor.cpp:1435
Tensor Reshape(const SizeVector &dst_shape) const
Definition: Tensor.cpp:671
static Tensor Empty(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor with uninitialized values.
Definition: Tensor.cpp:400
SizeVector GetShape() const
Definition: Tensor.h:1127
void Fill(S v)
Fill the whole Tensor with a scalar value, the scalar will be casted to the Tensor's Dtype.
Definition: Tensor.h:1400
Tensor T() const
Expects input to be <= 2-D Tensor by swapping dimension 0 and 1.
Definition: Tensor.cpp:1079
Tensor Slice(int64_t dim, int64_t start, int64_t stop, int64_t step=1) const
Definition: Tensor.cpp:857
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
const SizeVector & GetShapeRef() const
Definition: Tensor.h:1129
A bounding box that is aligned along the coordinate axes and defined by the min_bound and max_bound.
static AxisAlignedBoundingBox CreateFromPoints(const core::Tensor &points)
bool HasMaterial() const
Check if a material has been applied to this Geometry with SetMaterial.
void SetMaterial(const visualization::rendering::Material &material)
Set the material properties associate with this Geometry.
visualization::rendering::Material & GetMaterial()
Get material associated with this Geometry.
The base geometry class.
Definition: Geometry.h:23
GeometryType
Specifies possible geometry types.
Definition: Geometry.h:28
The Image class stores image with customizable rows, cols, channels, dtype and device.
Definition: Image.h:29
static constexpr bool HAVE_IPP
Do we use IPP for accelerating image processing operations?
Definition: Image.h:339
static Image FromLegacy(const cloudViewer::geometry::Image &image_legacy, const core::Device &Device=core::Device("CPU:0"))
Create from a legacy CloudViewer Image.
Definition: Image.cpp:505
Image To(const core::Device &device, bool copy=false) const
Transfer the image to a specified device.
Definition: Image.h:132
core::Tensor AsTensor() const
Returns the underlying Tensor of the Image.
Definition: Image.h:124
A LineSet contains points and lines joining them and optionally attributes on the points and lines.
Definition: LineSet.h:85
A bounding box oriented along an arbitrary frame of reference.
static OrientedBoundingBox CreateFromPoints(const core::Tensor &points, bool robust=false, MethodOBBCreate method=MethodOBBCreate::MINIMAL_APPROX)
A point cloud contains a list of 3D points.
Definition: PointCloud.h:82
void SetPointNormals(const core::Tensor &value)
Set the value of the "normals" attribute. Convenience function.
Definition: PointCloud.h:198
void SetPointColors(const core::Tensor &value)
Set the value of the "colors" attribute. Convenience function.
Definition: PointCloud.h:192
TriangleMesh ComputeConvexHull(bool joggle_inputs=false) const
PointCloud To(const core::Device &device, bool copy=false) const
Definition: PointCloud.cpp:112
A scene class with basic ray casting and closest point queries.
static core::Tensor CreateRaysPinhole(const core::Tensor &intrinsic_matrix, const core::Tensor &extrinsic_matrix, int width_px, int height_px)
Creates rays for the given camera parameters.
uint32_t AddTriangles(const core::Tensor &vertex_positions, const core::Tensor &triangle_indices)
Add a triangle mesh to the scene.
std::unordered_map< std::string, core::Tensor > CastRays(const core::Tensor &rays, const int nthreads=0) const
Computes the first intersection of the rays with the scene.
core::Tensor ComputeDistance(const core::Tensor &query_points, const int nthreads=0)
Computes the distance to the surface of the scene.
void AssertSizeSynchronized() const
Assert IsSizeSynchronized().
Definition: TensorMap.cpp:48
std::string GetPrimaryKey() const
Returns the primary key of the TensorMap.
Definition: TensorMap.h:159
std::unordered_set< std::string > GetKeySet() const
Returns a set with all keys.
Definition: TensorMap.h:162
bool Contains(const std::string &key) const
Definition: TensorMap.h:187
A triangle mesh contains vertices and triangles.
Definition: TriangleMesh.h:98
TriangleMesh SimplifyQuadricDecimation(double target_reduction, bool preserve_volume=true) const
TriangleMesh(const core::Device &device=core::Device("CPU:0"))
TriangleMesh & Translate(const core::Tensor &translation, bool relative=true)
Translates the VertexPositions of the TriangleMesh.
core::Tensor ComputeMetrics(const TriangleMesh &mesh2, std::vector< Metric > metrics={Metric::ChamferDistance}, MetricParameters params=MetricParameters()) const
void SetTriangleNormals(const core::Tensor &value)
Definition: TriangleMesh.h:298
TriangleMesh BooleanIntersection(const TriangleMesh &mesh, double tolerance=1e-6) const
double GetSurfaceArea() const
Function that computes the surface area of the mesh, i.e. the sum of the individual triangle surfaces...
static geometry::TriangleMesh FromLegacy(const ccMesh &mesh_legacy, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh SelectFacesByMask(const core::Tensor &mask) const
const TensorMap & GetVertexAttr() const
Getter for vertex_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:133
Image ProjectImagesToAlbedo(const std::vector< Image > &images, const std::vector< core::Tensor > &intrinsic_matrices, const std::vector< core::Tensor > &extrinsic_matrices, int tex_size=1024, bool update_material=true)
static std::unordered_map< std::string, geometry::TriangleMesh > FromTriangleMeshModel(const cloudViewer::visualization::rendering::TriangleMeshModel &model, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
std::unordered_map< std::string, core::Tensor > BakeTriangleAttrTextures(int size, const std::unordered_set< std::string > &triangle_attr={}, double margin=2., double fill=0., bool update_material=true)
std::string ToString() const
Text description.
TriangleMesh ClipPlane(const core::Tensor &point, const core::Tensor &normal) const
Clip mesh with a plane. This method clips the triangle mesh with the specified plane....
TriangleMesh ExtrudeRotation(double angle, const core::Tensor &axis, int resolution=16, double translation=0.0, bool capping=true) const
AxisAlignedBoundingBox GetAxisAlignedBoundingBox() const
Create an axis-aligned bounding box from vertex attribute "positions".
core::Device GetDevice() const override
Returns the device of the geometry.
Definition: TriangleMesh.h:721
std::unordered_map< std::string, core::Tensor > BakeVertexAttrTextures(int size, const std::unordered_set< std::string > &vertex_attr={}, double margin=2., double fill=0., bool update_material=true)
void SetVertexPositions(const core::Tensor &value)
Definition: TriangleMesh.h:261
TriangleMesh To(const core::Device &device, bool copy=false) const
core::Tensor GetNonManifoldEdges(bool allow_boundary_edges=true) const
TriangleMesh & NormalizeNormals()
Normalize both triangle normals and vertex normals to length 1.
void SetTriangleIndices(const core::Tensor &value)
Set the value of the "indices" attribute in triangle_attr_.
Definition: TriangleMesh.h:291
TriangleMesh BooleanUnion(const TriangleMesh &mesh, double tolerance=1e-6) const
PointCloud SamplePointsUniformly(size_t number_of_points, bool use_triangle_normal=false)
ccMesh ToLegacy() const
Convert to a legacy CloudViewer TriangleMesh.
void SetVertexNormals(const core::Tensor &value)
Definition: TriangleMesh.h:275
TriangleMesh & Scale(double scale, const core::Tensor &center)
Scales the VertexPositions of the TriangleMesh.
TriangleMesh FillHoles(double hole_size=1e6) const
bool HasVertexAttr(const std::string &key) const
Definition: TriangleMesh.h:314
TriangleMesh & Rotate(const core::Tensor &R, const core::Tensor &center)
Rotates the VertexPositions, VertexNormals and TriangleNormals (if exists).
TriangleMesh & ComputeTriangleAreas()
Function to compute triangle areas and save it as a triangle attribute "areas". Prints a warning,...
TriangleMesh ExtrudeLinear(const core::Tensor &vector, double scale=1.0, bool capping=true) const
TriangleMesh BooleanDifference(const TriangleMesh &mesh, double tolerance=1e-6) const
std::tuple< float, int, int > ComputeUVAtlas(size_t size=512, float gutter=1.0f, float max_stretch=1.f/6, int parallel_partitions=1, int nthreads=0)
bool HasTriangleAttr(const std::string &key) const
Definition: TriangleMesh.h:343
LineSet SlicePlane(const core::Tensor &point, const core::Tensor &normal, const std::vector< double > contour_values={0.0}) const
Extract contour slices given a plane. This method extracts slices as LineSet from the mesh at specifi...
TriangleMesh ComputeConvexHull(bool joggle_inputs=false) const
TriangleMesh & ComputeVertexNormals(bool normalized=true)
Function to compute vertex normals, usually called before rendering.
void SetVertexAttr(const std::string &key, const core::Tensor &value)
Definition: TriangleMesh.h:254
void SetVertexColors(const core::Tensor &value)
Definition: TriangleMesh.h:268
TriangleMesh SelectByIndex(const core::Tensor &indices, bool copy_attributes=true) const
bool IsEmpty() const override
Returns !HasVertexPositions(), triangles are ignored.
Definition: TriangleMesh.h:626
void SetTriangleAttr(const std::string &key, const core::Tensor &value)
Definition: TriangleMesh.h:285
TriangleMesh & Transform(const core::Tensor &transformation)
Transforms the VertexPositions, VertexNormals and TriangleNormals (if exist) of the TriangleMesh.
const TensorMap & GetTriangleAttr() const
Getter for triangle_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:159
TriangleMesh & ComputeTriangleNormals(bool normalized=true)
Function to compute triangle normals, usually called before rendering.
OrientedBoundingBox GetOrientedBoundingBox() const
Create an oriented bounding box from vertex attribute "positions".
static Material FromMaterialRecord(const MaterialRecord &mat)
Convert from MaterialRecord.
Definition: Material.cpp:149
void SetAlbedoMap(const t::geometry::Image &image)
Definition: Material.h:208
const t::geometry::Image & GetAlbedoMap() const
Definition: Material.h:160
double colors[3]
double normals[3]
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
int min(int a, int b)
Definition: cutil_math.h:53
Helper functions for the ml ops.
std::vector< Eigen::Vector2d > TensorToEigenVector2dVector(const core::Tensor &tensor)
Converts a tensor of shape (N, 2) to std::vector<Eigen::Vector2d>. An exception will be thrown if the...
std::vector< Eigen::Vector3d > TensorToEigenVector3dVector(const core::Tensor &tensor)
Converts a tensor of shape (N, 3) to std::vector<Eigen::Vector3d>. An exception will be thrown if the...
core::Tensor EigenVector3dVectorToTensor(const std::vector< Eigen::Vector3d > &values, core::Dtype dtype, const core::Device &device)
Converts a vector of Eigen::Vector3d to a (N, 3) tensor. This function also takes care of dtype conve...
std::vector< Eigen::Vector3i > TensorToEigenVector3iVector(const core::Tensor &tensor)
Converts a tensor of shape (N, 3) to std::vector<Eigen::Vector3i>. An exception will be thrown if the...
core::Tensor EigenVector2dVectorToTensor(const std::vector< Eigen::Vector2d > &values, core::Dtype dtype, const core::Device &device)
Converts a vector of Eigen::Vector2d to a (N, 2) tensor. This function also takes care of dtype conve...
core::Tensor EigenVector3iVectorToTensor(const std::vector< Eigen::Vector3i > &values, core::Dtype dtype, const core::Device &device)
Converts a vector of Eigen::Vector3i to a (N, 3) tensor. This function also takes care of dtype conve...
const Dtype Undefined
Definition: Dtype.cpp:41
const Dtype Bool
Definition: Dtype.cpp:52
const Dtype Int64
Definition: Dtype.cpp:47
const Dtype UInt32
Definition: Dtype.cpp:50
const Dtype UInt8
Definition: Dtype.cpp:48
const Dtype Float64
Definition: Dtype.cpp:43
const Dtype UInt16
Definition: Dtype.cpp:49
constexpr utility::nullopt_t None
Definition: TensorKey.h:20
const Dtype Int32
Definition: Dtype.cpp:46
const Dtype Float32
Definition: Dtype.cpp:42
::ccPointCloud PointCloud
Definition: PointCloud.h:19
constexpr float EPS
Definition: IoUImpl.h:20
void Remap(const core::Tensor &src_im, const core::Tensor &dst2src_xmap, const core::Tensor &dst2src_ymap, core::Tensor &dst_im, Image::InterpType interp_type)
Definition: IPPImage.cpp:311
std::tuple< int, core::Tensor > PCAPartition(core::Tensor &points, int max_points)
void Project(core::Tensor &depth, utility::optional< std::reference_wrapper< core::Tensor >> image_colors, const core::Tensor &points, utility::optional< std::reference_wrapper< const core::Tensor >> colors, const core::Tensor &intrinsics, const core::Tensor &extrinsics, float depth_scale, float depth_max)
Definition: PointCloud.cpp:63
void TransformNormals(const core::Tensor &transformation, core::Tensor &normals)
Definition: Transform.cpp:41
void TransformPoints(const core::Tensor &transformation, core::Tensor &points)
Definition: Transform.cpp:20
void RotatePoints(const core::Tensor &R, core::Tensor &points, const core::Tensor &center)
Definition: Transform.cpp:63
void RotateNormals(const core::Tensor &R, core::Tensor &normals)
Definition: Transform.cpp:88
void ComputeTriangleNormalsCPU(const core::Tensor &vertices, const core::Tensor &triangles, core::Tensor &normals)
void ComputeVertexNormalsCPU(const core::Tensor &triangles, const core::Tensor &triangle_normals, core::Tensor &vertex_normals)
void NormalizeNormalsCPU(core::Tensor &normals)
void ComputeTriangleAreasCPU(const core::Tensor &vertices, const core::Tensor &triangles, core::Tensor &triangle_areas)
std::array< core::Tensor, 3 > SamplePointsUniformlyCPU(const core::Tensor &triangles, const core::Tensor &vertices, const core::Tensor &triangle_areas, const core::Tensor &vertex_normals, const core::Tensor &vertex_colors, const core::Tensor &triangle_normals, const core::Tensor &texture_uvs, const core::Tensor &albedo, size_t number_of_points)
std::tuple< float, int, int > ComputeUVAtlas(TriangleMesh &mesh, const size_t width, const size_t height, const float gutter, const float max_stretch, int parallel_partitions, int nthreads)
vtkSmartPointer< vtkPolyData > CreateVtkPolyDataFromGeometry(const Geometry &geometry, const std::unordered_set< std::string > &point_attr_include, const std::unordered_set< std::string > &face_attr_include, const std::unordered_set< std::string > &point_attr_exclude, const std::unordered_set< std::string > &face_attr_exclude, bool copy)
Definition: VtkUtils.cpp:369
TriangleMesh CreateTriangleMeshFromVtkPolyData(vtkPolyData *polydata, bool copy)
Definition: VtkUtils.cpp:431
CLOUDVIEWER_LOCAL TriangleMesh ExtrudeLinearTriangleMesh(const Geometry &geometry, const core::Tensor &vector, double scale, bool capping)
Definition: VtkUtils.cpp:580
CLOUDVIEWER_LOCAL TriangleMesh ExtrudeRotationTriangleMesh(const Geometry &geometry, const double angle, const core::Tensor &axis, int resolution, double translation, bool capping)
Definition: VtkUtils.cpp:529
CLOUDVIEWER_LOCAL LineSet CreateLineSetFromVtkPolyData(vtkPolyData *polydata, bool copy)
Definition: VtkUtils.cpp:473
static void UpdateTriangleIndicesByVertexMask(core::Tensor &tris_cpu, const core::Tensor &vertex_mask)
static bool IsNegative(T val)
brief Static negative checker for signed integer types
static void CopyAttributesByMasks(TriangleMesh &dst, const TriangleMesh &src, const core::Tensor &vertex_mask, const core::Tensor &tri_mask)
static Edge< T > GetOrderedEdge(T vidx0, T vidx1)
brief Helper function to get an edge with ordered vertex indices.
static std::unordered_map< Edge< T >, std::vector< size_t >, utility::hash_tuple< Edge< T > > > GetEdgeToTrianglesMap(const core::Tensor &tris_cpu)
core::Tensor ComputeMetricsCommon(core::Tensor distance12, core::Tensor distance21, std::vector< Metric > metrics, MetricParameters params)
Definition: Metrics.cpp:16
static core::Tensor ComputeTriangleAreasHelper(const TriangleMesh &mesh)
void InclusivePrefixSum(const Tin *first, const Tin *last, Tout *out)
Definition: ParallelScan.h:66
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
struct Index Index
Definition: sqlite3.c:14646
Holder for various parameters required by metrics.
Definition: Geometry.h:103
std::vector< visualization::rendering::MaterialRecord > materials_
Definition: Model.h:26
Definition: lsd.c:149