From 198d85e2e1c6c81ab7fdc3a47db452254712fa0f Mon Sep 17 00:00:00 2001 From: aditya18007 Date: Thu, 12 Dec 2024 21:55:27 +0530 Subject: [PATCH] moved code --- cpp/open3d/t/geometry/TriangleMesh.cpp | 1716 ++----------------- cpp/open3d/t/geometry/TriangleMesh.h | 1204 ++----------- cpp/pybind/t/geometry/trianglemesh.cpp | 5 +- cpp/tests/t/geometry/TriangleMesh.cpp | 64 + python/test/t/geometry/test_trianglemesh.py | 60 + 5 files changed, 440 insertions(+), 2609 deletions(-) diff --git a/cpp/open3d/t/geometry/TriangleMesh.cpp b/cpp/open3d/t/geometry/TriangleMesh.cpp index 05e8cc7b66f..aabec93b8fa 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.cpp +++ b/cpp/open3d/t/geometry/TriangleMesh.cpp @@ -5,1620 +5,178 @@ // SPDX-License-Identifier: MIT // ---------------------------------------------------------------------------- -#include "open3d/t/geometry/TriangleMesh.h" +#include "open3d/t/geometry/TensorMap.h" -#include -#include -#include -#include -#include -#include -#include +#include -#include +#include #include #include -#include "open3d/core/CUDAUtils.h" -#include "open3d/core/Device.h" -#include "open3d/core/Dtype.h" -#include "open3d/core/EigenConverter.h" -#include "open3d/core/ShapeUtil.h" -#include "open3d/core/Tensor.h" -#include "open3d/core/TensorCheck.h" -#include "open3d/t/geometry/LineSet.h" -#include "open3d/t/geometry/PointCloud.h" -#include "open3d/t/geometry/RaycastingScene.h" -#include "open3d/t/geometry/VtkUtils.h" -#include "open3d/t/geometry/kernel/Metrics.h" -#include "open3d/t/geometry/kernel/PCAPartition.h" -#include "open3d/t/geometry/kernel/PointCloud.h" -#include "open3d/t/geometry/kernel/Transform.h" -#include "open3d/t/geometry/kernel/TriangleMesh.h" -#include "open3d/t/geometry/kernel/UVUnwrapping.h" -#include "open3d/utility/ParallelScan.h" +#include "open3d/utility/Logging.h" namespace open3d { namespace t { namespace geometry { -class PointCloud; // forward declaration - -TriangleMesh::TriangleMesh(const core::Device &device) - : Geometry(Geometry::GeometryType::TriangleMesh, 3), - device_(device), - vertex_attr_(TensorMap("positions")), - triangle_attr_(TensorMap("indices")) {} - -TriangleMesh::TriangleMesh(const core::Tensor &vertex_positions, - const core::Tensor &triangle_indices) - : TriangleMesh([&]() { - if (vertex_positions.GetDevice() != triangle_indices.GetDevice()) { - utility::LogError( - "vertex_positions' device {} does not match " - "triangle_indices' device {}.", - vertex_positions.GetDevice().ToString(), - triangle_indices.GetDevice().ToString()); - } - return vertex_positions.GetDevice(); - }()) { - SetVertexPositions(vertex_positions); - SetTriangleIndices(triangle_indices); -} - -std::string TriangleMesh::ToString() const { - size_t num_vertices = 0; - std::string vertex_dtype_str = ""; - size_t num_triangles = 0; - std::string triangles_dtype_str = ""; - if (vertex_attr_.count(vertex_attr_.GetPrimaryKey())) { - num_vertices = GetVertexPositions().GetLength(); - vertex_dtype_str = fmt::format( - " ({})", GetVertexPositions().GetDtype().ToString()); - } - if (triangle_attr_.count(triangle_attr_.GetPrimaryKey())) { - num_triangles = GetTriangleIndices().GetLength(); - triangles_dtype_str = fmt::format( - " ({})", GetTriangleIndices().GetDtype().ToString()); - } - - auto str = fmt::format( - "TriangleMesh on {} [{} vertices{} and {} triangles{}].", - GetDevice().ToString(), num_vertices, vertex_dtype_str, - num_triangles, triangles_dtype_str); - - std::string vertices_attr_str = "\nVertex Attributes:"; - if ((vertex_attr_.size() - - vertex_attr_.count(vertex_attr_.GetPrimaryKey())) == 0) { - vertices_attr_str += " None."; - } else { - for (const auto &kv : vertex_attr_) { - if (kv.first != "positions") { - vertices_attr_str += - fmt::format(" {} (dtype = {}, shape = {}),", kv.first, - kv.second.GetDtype().ToString(), - kv.second.GetShape().ToString()); - } - } - vertices_attr_str[vertices_attr_str.size() - 1] = '.'; - } - - std::string triangles_attr_str = "\nTriangle Attributes:"; - if ((triangle_attr_.size() - - triangle_attr_.count(triangle_attr_.GetPrimaryKey())) == 0) { - triangles_attr_str += " None."; - } else { - for (const auto &kv : triangle_attr_) { - if (kv.first != "indices") { - triangles_attr_str += - fmt::format(" {} (dtype = {}, shape = {}),", kv.first, - kv.second.GetDtype().ToString(), - kv.second.GetShape().ToString()); - } - } - triangles_attr_str[triangles_attr_str.size() - 1] = '.'; - } - - return str + vertices_attr_str + triangles_attr_str; -} - -TriangleMesh &TriangleMesh::Transform(const core::Tensor &transformation) { - core::AssertTensorShape(transformation, {4, 4}); - - kernel::transform::TransformPoints(transformation, GetVertexPositions()); - if (HasVertexNormals()) { - kernel::transform::TransformNormals(transformation, GetVertexNormals()); - } - if (HasTriangleNormals()) { - kernel::transform::TransformNormals(transformation, - GetTriangleNormals()); - } - - return *this; -} - -TriangleMesh &TriangleMesh::Translate(const core::Tensor &translation, - bool relative) { - core::AssertTensorShape(translation, {3}); - - core::Tensor transform = - translation.To(GetDevice(), GetVertexPositions().GetDtype()); - - if (!relative) { - transform -= GetCenter(); - } - GetVertexPositions() += transform; - return *this; -} - -TriangleMesh &TriangleMesh::Scale(double scale, const core::Tensor ¢er) { - core::AssertTensorShape(center, {3}); - core::AssertTensorDevice(center, device_); - - const core::Tensor center_d = - center.To(GetDevice(), GetVertexPositions().GetDtype()); - - GetVertexPositions().Sub_(center_d).Mul_(scale).Add_(center_d); - return *this; -} - -TriangleMesh &TriangleMesh::Rotate(const core::Tensor &R, - const core::Tensor ¢er) { - core::AssertTensorShape(R, {3, 3}); - core::AssertTensorShape(center, {3}); - - kernel::transform::RotatePoints(R, GetVertexPositions(), center); - if (HasVertexNormals()) { - kernel::transform::RotateNormals(R, GetVertexNormals()); - } - if (HasTriangleNormals()) { - kernel::transform::RotateNormals(R, GetTriangleNormals()); - } - return *this; -} - -TriangleMesh &TriangleMesh::NormalizeNormals() { - if (HasVertexNormals()) { - SetVertexNormals(GetVertexNormals().Contiguous()); - core::Tensor &vertex_normals = GetVertexNormals(); - if (IsCPU()) { - kernel::trianglemesh::NormalizeNormalsCPU(vertex_normals); - } else if (IsCUDA()) { - CUDA_CALL(kernel::trianglemesh::NormalizeNormalsCUDA, - vertex_normals); - } else { - utility::LogError("Unimplemented device"); - } - } else { - utility::LogWarning("TriangleMesh has no vertex normals."); - } - - if (HasTriangleNormals()) { - SetTriangleNormals(GetTriangleNormals().Contiguous()); - core::Tensor &triangle_normals = GetTriangleNormals(); - if (IsCPU()) { - kernel::trianglemesh::NormalizeNormalsCPU(triangle_normals); - } else if (IsCUDA()) { - CUDA_CALL(kernel::trianglemesh::NormalizeNormalsCUDA, - triangle_normals); - } else { - utility::LogError("Unimplemented device"); - } - } else { - utility::LogWarning("TriangleMesh has no triangle normals."); - } - - return *this; -} - -TriangleMesh &TriangleMesh::ComputeTriangleNormals(bool normalized) { - if (IsEmpty()) { - utility::LogWarning("TriangleMesh is empty."); - return *this; - } - - if (!HasTriangleIndices()) { - utility::LogWarning("TriangleMesh has no triangle indices."); - return *this; - } - - const int64_t triangle_num = GetTriangleIndices().GetLength(); - const core::Dtype dtype = GetVertexPositions().GetDtype(); - core::Tensor triangle_normals({triangle_num, 3}, dtype, GetDevice()); - SetVertexPositions(GetVertexPositions().Contiguous()); - SetTriangleIndices(GetTriangleIndices().Contiguous()); - - if (IsCPU()) { - kernel::trianglemesh::ComputeTriangleNormalsCPU( - GetVertexPositions(), GetTriangleIndices(), triangle_normals); - } else if (IsCUDA()) { - CUDA_CALL(kernel::trianglemesh::ComputeTriangleNormalsCUDA, - GetVertexPositions(), GetTriangleIndices(), triangle_normals); - } else { - utility::LogError("Unimplemented device"); - } - - SetTriangleNormals(triangle_normals); - - if (normalized) { - NormalizeNormals(); - } - - return *this; -} - -TriangleMesh &TriangleMesh::ComputeVertexNormals(bool normalized) { - if (IsEmpty()) { - utility::LogWarning("TriangleMesh is empty."); - return *this; - } - - if (!HasTriangleIndices()) { - utility::LogWarning("TriangleMesh has no triangle indices."); - return *this; - } - - ComputeTriangleNormals(false); - - const int64_t vertex_num = GetVertexPositions().GetLength(); - const core::Dtype dtype = GetVertexPositions().GetDtype(); - core::Tensor vertex_normals = - core::Tensor::Zeros({vertex_num, 3}, dtype, GetDevice()); - - SetTriangleNormals(GetTriangleNormals().Contiguous()); - SetTriangleIndices(GetTriangleIndices().Contiguous()); - - if (IsCPU()) { - kernel::trianglemesh::ComputeVertexNormalsCPU( - GetTriangleIndices(), GetTriangleNormals(), vertex_normals); - } else if (IsCUDA()) { - CUDA_CALL(kernel::trianglemesh::ComputeVertexNormalsCUDA, - GetTriangleIndices(), GetTriangleNormals(), vertex_normals); - } else { - utility::LogError("Unimplemented device"); - } - - SetVertexNormals(vertex_normals); - if (normalized) { - NormalizeNormals(); - } - - return *this; -} - -static core::Tensor ComputeTriangleAreasHelper(const TriangleMesh &mesh) { - const int64_t triangle_num = mesh.GetTriangleIndices().GetLength(); - const core::Dtype dtype = mesh.GetVertexPositions().GetDtype(); - core::Tensor triangle_areas({triangle_num}, dtype, mesh.GetDevice()); - if (mesh.IsCPU()) { - kernel::trianglemesh::ComputeTriangleAreasCPU( - mesh.GetVertexPositions().Contiguous(), - mesh.GetTriangleIndices().Contiguous(), triangle_areas); - } else if (mesh.IsCUDA()) { - CUDA_CALL(kernel::trianglemesh::ComputeTriangleAreasCUDA, - mesh.GetVertexPositions().Contiguous(), - mesh.GetTriangleIndices().Contiguous(), triangle_areas); - } else { - utility::LogError("Unimplemented device"); - } - - return triangle_areas; -} - -TriangleMesh &TriangleMesh::ComputeTriangleAreas() { - if (IsEmpty()) { - utility::LogWarning("TriangleMesh is empty."); - return *this; - } - - if (!HasTriangleIndices()) { - SetTriangleAttr("areas", core::Tensor::Empty( - {0}, GetVertexPositions().GetDtype(), - GetDevice())); - utility::LogWarning("TriangleMesh has no triangle indices."); - return *this; - } - if (HasTriangleAttr("areas")) { - utility::LogWarning( - "TriangleMesh already has triangle areas: remove " - "'areas' triangle attribute if you'd like to update."); - return *this; - } - - core::Tensor triangle_areas = ComputeTriangleAreasHelper(*this); - SetTriangleAttr("areas", triangle_areas); - - return *this; -} - -double TriangleMesh::GetSurfaceArea() const { - double surface_area = 0; - if (IsEmpty()) { - utility::LogWarning("TriangleMesh is empty."); - return surface_area; - } - - if (!HasTriangleIndices()) { - utility::LogWarning("TriangleMesh has no triangle indices."); - return surface_area; - } - - core::Tensor triangle_areas = ComputeTriangleAreasHelper(*this); - surface_area = triangle_areas.Sum({0}).To(core::Float64).Item(); - - return surface_area; -} - -geometry::TriangleMesh TriangleMesh::FromLegacy( - const open3d::geometry::TriangleMesh &mesh_legacy, - core::Dtype float_dtype, - core::Dtype int_dtype, - const core::Device &device) { - if (float_dtype != core::Float32 && float_dtype != core::Float64) { - utility::LogError("float_dtype must be Float32 or Float64, but got {}.", - float_dtype.ToString()); - } - if (int_dtype != core::Int32 && int_dtype != core::Int64) { - utility::LogError("int_dtype must be Int32 or Int64, but got {}.", - int_dtype.ToString()); - } - - TriangleMesh mesh(device); - if (mesh_legacy.HasVertices()) { - mesh.SetVertexPositions( - core::eigen_converter::EigenVector3dVectorToTensor( - mesh_legacy.vertices_, float_dtype, device)); - } else { - utility::LogWarning("Creating from empty legacy TriangleMesh."); - } - if (mesh_legacy.HasVertexColors()) { - mesh.SetVertexColors(core::eigen_converter::EigenVector3dVectorToTensor( - mesh_legacy.vertex_colors_, float_dtype, device)); - } - if (mesh_legacy.HasVertexNormals()) { - mesh.SetVertexNormals( - core::eigen_converter::EigenVector3dVectorToTensor( - mesh_legacy.vertex_normals_, float_dtype, device)); - } - if (mesh_legacy.HasTriangles()) { - mesh.SetTriangleIndices( - core::eigen_converter::EigenVector3iVectorToTensor( - mesh_legacy.triangles_, int_dtype, device)); - } - if (mesh_legacy.HasTriangleNormals()) { - mesh.SetTriangleNormals( - core::eigen_converter::EigenVector3dVectorToTensor( - mesh_legacy.triangle_normals_, float_dtype, device)); - } - if (mesh_legacy.HasTriangleUvs()) { - mesh.SetTriangleAttr( - "texture_uvs", - core::eigen_converter::EigenVector2dVectorToTensor( - mesh_legacy.triangle_uvs_, float_dtype, device) - .Reshape({-1, 3, 2})); - } - - // Convert first material only if one or more are present - if (mesh_legacy.materials_.size() > 0) { - const auto &mat = mesh_legacy.materials_.begin()->second; - auto &tmat = mesh.GetMaterial(); - tmat.SetDefaultProperties(); - tmat.SetBaseColor(Eigen::Vector4f{mat.baseColor.f4}); - tmat.SetBaseRoughness(mat.baseRoughness); - tmat.SetBaseMetallic(mat.baseMetallic); - tmat.SetBaseReflectance(mat.baseReflectance); - tmat.SetAnisotropy(mat.baseAnisotropy); - tmat.SetBaseClearcoat(mat.baseClearCoat); - tmat.SetBaseClearcoatRoughness(mat.baseClearCoatRoughness); - // no emissive_color in legacy mesh material - if (mat.albedo) tmat.SetAlbedoMap(Image::FromLegacy(*mat.albedo)); - if (mat.normalMap) tmat.SetNormalMap(Image::FromLegacy(*mat.normalMap)); - if (mat.roughness) - tmat.SetRoughnessMap(Image::FromLegacy(*mat.roughness)); - if (mat.metallic) tmat.SetMetallicMap(Image::FromLegacy(*mat.metallic)); - if (mat.reflectance) - tmat.SetReflectanceMap(Image::FromLegacy(*mat.reflectance)); - if (mat.ambientOcclusion) - tmat.SetAOMap(Image::FromLegacy(*mat.ambientOcclusion)); - if (mat.clearCoat) - tmat.SetClearcoatMap(Image::FromLegacy(*mat.clearCoat)); - if (mat.clearCoatRoughness) - tmat.SetClearcoatRoughnessMap( - Image::FromLegacy(*mat.clearCoatRoughness)); - if (mat.anisotropy) - tmat.SetAnisotropyMap(Image::FromLegacy(*mat.anisotropy)); - } - if (mesh_legacy.materials_.size() > 1) { - utility::LogWarning( - "Legacy mesh has more than 1 material which is not supported " - "by Tensor-based mesh. Only material {} was converted.", - mesh_legacy.materials_.begin()->first); - } - return mesh; -} - -open3d::geometry::TriangleMesh TriangleMesh::ToLegacy() const { - open3d::geometry::TriangleMesh mesh_legacy; - if (HasVertexPositions()) { - mesh_legacy.vertices_ = - core::eigen_converter::TensorToEigenVector3dVector( - GetVertexPositions()); - } - if (HasVertexColors()) { - mesh_legacy.vertex_colors_ = - core::eigen_converter::TensorToEigenVector3dVector( - GetVertexColors()); - } - if (HasVertexNormals()) { - mesh_legacy.vertex_normals_ = - core::eigen_converter::TensorToEigenVector3dVector( - GetVertexNormals()); - } - if (HasTriangleIndices()) { - mesh_legacy.triangles_ = - core::eigen_converter::TensorToEigenVector3iVector( - GetTriangleIndices()); - } - if (HasTriangleNormals()) { - mesh_legacy.triangle_normals_ = - core::eigen_converter::TensorToEigenVector3dVector( - GetTriangleNormals()); - } - if (HasTriangleAttr("texture_uvs")) { - mesh_legacy.triangle_uvs_ = - core::eigen_converter::TensorToEigenVector2dVector( - GetTriangleAttr("texture_uvs").Reshape({-1, 2})); - } - if (HasVertexAttr("texture_uvs")) { - utility::LogWarning("{}", - "texture_uvs as a vertex attribute is not " - "supported by legacy TriangleMesh. Ignored."); - } - - // Convert material if the t geometry has a valid one - auto &tmat = GetMaterial(); - if (tmat.IsValid()) { - mesh_legacy.materials_.emplace_back(); - mesh_legacy.materials_.front().first = "Mat1"; - auto &legacy_mat = mesh_legacy.materials_.front().second; - // Convert scalar properties - if (tmat.HasBaseColor()) { - legacy_mat.baseColor.f4[0] = tmat.GetBaseColor().x(); - legacy_mat.baseColor.f4[1] = tmat.GetBaseColor().y(); - legacy_mat.baseColor.f4[2] = tmat.GetBaseColor().z(); - legacy_mat.baseColor.f4[3] = tmat.GetBaseColor().w(); - } - if (tmat.HasBaseRoughness()) { - legacy_mat.baseRoughness = tmat.GetBaseRoughness(); - } - if (tmat.HasBaseMetallic()) { - legacy_mat.baseMetallic = tmat.GetBaseMetallic(); - } - if (tmat.HasBaseReflectance()) { - legacy_mat.baseReflectance = tmat.GetBaseReflectance(); - } - if (tmat.HasBaseClearcoat()) { - legacy_mat.baseClearCoat = tmat.GetBaseClearcoat(); - } - if (tmat.HasBaseClearcoatRoughness()) { - legacy_mat.baseClearCoatRoughness = - tmat.GetBaseClearcoatRoughness(); - } - if (tmat.HasAnisotropy()) { - legacy_mat.baseAnisotropy = tmat.GetAnisotropy(); - } - // Convert maps - if (tmat.HasAlbedoMap()) { - legacy_mat.albedo = std::make_shared(); - *legacy_mat.albedo = tmat.GetAlbedoMap().ToLegacy(); - } - if (tmat.HasNormalMap()) { - legacy_mat.normalMap = std::make_shared(); - *legacy_mat.normalMap = tmat.GetNormalMap().ToLegacy(); - } - if (tmat.HasAOMap()) { - legacy_mat.ambientOcclusion = - std::make_shared(); - *legacy_mat.ambientOcclusion = tmat.GetAOMap().ToLegacy(); - } - if (tmat.HasMetallicMap()) { - legacy_mat.metallic = std::make_shared(); - *legacy_mat.metallic = tmat.GetMetallicMap().ToLegacy(); - } - if (tmat.HasRoughnessMap()) { - legacy_mat.roughness = std::make_shared(); - *legacy_mat.roughness = tmat.GetRoughnessMap().ToLegacy(); - } - if (tmat.HasReflectanceMap()) { - legacy_mat.reflectance = - std::make_shared(); - *legacy_mat.reflectance = tmat.GetReflectanceMap().ToLegacy(); - } - if (tmat.HasClearcoatMap()) { - legacy_mat.clearCoat = std::make_shared(); - *legacy_mat.clearCoat = tmat.GetClearcoatMap().ToLegacy(); - } - if (tmat.HasClearcoatRoughnessMap()) { - legacy_mat.clearCoatRoughness = - std::make_shared(); - *legacy_mat.clearCoatRoughness = - tmat.GetClearcoatRoughnessMap().ToLegacy(); - } - if (tmat.HasAnisotropyMap()) { - legacy_mat.anisotropy = std::make_shared(); - *legacy_mat.anisotropy = tmat.GetAnisotropyMap().ToLegacy(); - } - } - - return mesh_legacy; -} - -std::unordered_map -TriangleMesh::FromTriangleMeshModel( - const open3d::visualization::rendering::TriangleMeshModel &model, - core::Dtype float_dtype, - core::Dtype int_dtype, - const core::Device &device) { - std::unordered_map tmeshes; - for (const auto &mobj : model.meshes_) { - auto tmesh = TriangleMesh::FromLegacy(*mobj.mesh, float_dtype, - int_dtype, device); - // material textures will be on the CPU. GPU resident texture images is - // not yet supported. See comment in Material.cpp - tmesh.SetMaterial( - visualization::rendering::Material::FromMaterialRecord( - model.materials_[mobj.material_idx])); - tmeshes.emplace(mobj.mesh_name, tmesh); - } - return tmeshes; -} - -TriangleMesh TriangleMesh::To(const core::Device &device, bool copy) const { - if (!copy && GetDevice() == device) { - return *this; - } - TriangleMesh mesh(device); - for (const auto &kv : triangle_attr_) { - mesh.SetTriangleAttr(kv.first, kv.second.To(device, /*copy=*/true)); - } - for (const auto &kv : vertex_attr_) { - mesh.SetVertexAttr(kv.first, kv.second.To(device, /*copy=*/true)); - } - return mesh; -} - -TriangleMesh TriangleMesh::ComputeConvexHull(bool joggle_inputs) const { - PointCloud pcd(GetVertexPositions()); - return pcd.ComputeConvexHull(); -} - -TriangleMesh TriangleMesh::ClipPlane(const core::Tensor &point, - const core::Tensor &normal) const { - using namespace vtkutils; - core::AssertTensorShape(point, {3}); - core::AssertTensorShape(normal, {3}); - // allow int types for convenience - core::AssertTensorDtypes( - point, {core::Float32, core::Float64, core::Int32, core::Int64}); - core::AssertTensorDtypes( - normal, {core::Float32, core::Float64, core::Int32, core::Int64}); - - auto point_ = point.To(core::Device(), core::Float64).Contiguous(); - auto normal_ = normal.To(core::Device(), core::Float64).Contiguous(); - - auto polydata = CreateVtkPolyDataFromGeometry( - *this, GetVertexAttr().GetKeySet(), GetTriangleAttr().GetKeySet(), - {}, {}, false); - - vtkNew clipPlane; - clipPlane->SetNormal(normal_.GetDataPtr()); - clipPlane->SetOrigin(point_.GetDataPtr()); - vtkNew clipper; - clipper->SetInputData(polydata); - clipper->SetClipFunction(clipPlane); - vtkNew cleaner; - cleaner->SetInputConnection(clipper->GetOutputPort()); - cleaner->Update(); - auto clipped_polydata = cleaner->GetOutput(); - return CreateTriangleMeshFromVtkPolyData(clipped_polydata); -} - -LineSet TriangleMesh::SlicePlane( - const core::Tensor &point, - const core::Tensor &normal, - const std::vector contour_values) const { - using namespace vtkutils; - core::AssertTensorShape(point, {3}); - core::AssertTensorShape(normal, {3}); - // allow int types for convenience - core::AssertTensorDtypes( - point, {core::Float32, core::Float64, core::Int32, core::Int64}); - core::AssertTensorDtypes( - normal, {core::Float32, core::Float64, core::Int32, core::Int64}); - - auto point_ = point.To(core::Device(), core::Float64).Contiguous(); - auto normal_ = normal.To(core::Device(), core::Float64).Contiguous(); - - auto polydata = CreateVtkPolyDataFromGeometry( - *this, GetVertexAttr().GetKeySet(), {}, {}, {}, false); - - vtkNew clipPlane; - clipPlane->SetNormal(normal_.GetDataPtr()); - clipPlane->SetOrigin(point_.GetDataPtr()); - - vtkNew cutter; - cutter->SetInputData(polydata); - cutter->SetCutFunction(clipPlane); - cutter->GenerateTrianglesOff(); - cutter->SetNumberOfContours(contour_values.size()); - int i = 0; - for (double value : contour_values) { - cutter->SetValue(i++, value); - } - cutter->Update(); - auto slices_polydata = cutter->GetOutput(); - - return CreateLineSetFromVtkPolyData(slices_polydata); -} - -TriangleMesh TriangleMesh::SimplifyQuadricDecimation( - double target_reduction, bool preserve_volume) const { - using namespace vtkutils; - if (target_reduction >= 1.0 || target_reduction < 0) { - utility::LogError( - "target_reduction must be in the range [0,1) but is {}", - target_reduction); - } - - // exclude attributes because they will not be preserved - auto polydata = CreateVtkPolyDataFromGeometry(*this, {}, {}, {}, {}, false); - - vtkNew decimate; - decimate->SetInputData(polydata); - decimate->SetTargetReduction(target_reduction); - decimate->SetVolumePreservation(preserve_volume); - decimate->Update(); - auto decimated_polydata = decimate->GetOutput(); - - return CreateTriangleMeshFromVtkPolyData(decimated_polydata); -} - -namespace { -TriangleMesh BooleanOperation(const TriangleMesh &mesh_A, - const TriangleMesh &mesh_B, - double tolerance, - int op) { - using namespace vtkutils; - // exclude triangle attributes because they will not be preserved - auto polydata_A = CreateVtkPolyDataFromGeometry( - mesh_A, mesh_A.GetVertexAttr().GetKeySet(), {}, {}, {}, false); - auto polydata_B = CreateVtkPolyDataFromGeometry( - mesh_B, mesh_B.GetVertexAttr().GetKeySet(), {}, {}, {}, false); - - // clean meshes before passing them to the boolean operation - vtkNew cleaner_A; - cleaner_A->SetInputData(polydata_A); - - vtkNew cleaner_B; - cleaner_B->SetInputData(polydata_B); - - vtkNew boolean_filter; - boolean_filter->SetOperation(op); - boolean_filter->SetTolerance(tolerance); - boolean_filter->SetInputConnection(0, cleaner_A->GetOutputPort()); - boolean_filter->SetInputConnection(1, cleaner_B->GetOutputPort()); - boolean_filter->Update(); - auto out_polydata = boolean_filter->GetOutput(); - - return CreateTriangleMeshFromVtkPolyData(out_polydata); -} -} // namespace - -TriangleMesh TriangleMesh::BooleanUnion(const TriangleMesh &mesh, - double tolerance) const { - return BooleanOperation(*this, mesh, tolerance, - vtkBooleanOperationPolyDataFilter::VTK_UNION); -} - -TriangleMesh TriangleMesh::BooleanIntersection(const TriangleMesh &mesh, - double tolerance) const { - return BooleanOperation( - *this, mesh, tolerance, - vtkBooleanOperationPolyDataFilter::VTK_INTERSECTION); -} - -TriangleMesh TriangleMesh::BooleanDifference(const TriangleMesh &mesh, - double tolerance) const { - return BooleanOperation(*this, mesh, tolerance, - vtkBooleanOperationPolyDataFilter::VTK_DIFFERENCE); -} - -AxisAlignedBoundingBox TriangleMesh::GetAxisAlignedBoundingBox() const { - return AxisAlignedBoundingBox::CreateFromPoints(GetVertexPositions()); -} - -OrientedBoundingBox TriangleMesh::GetOrientedBoundingBox() const { - return OrientedBoundingBox::CreateFromPoints(GetVertexPositions()); -} - -TriangleMesh TriangleMesh::FillHoles(double hole_size) const { - using namespace vtkutils; - // do not include triangle attributes because they will not be preserved by - // the hole filling algorithm - auto polydata = CreateVtkPolyDataFromGeometry( - *this, GetVertexAttr().GetKeySet(), {}, {}, {}, false); - vtkNew fill_holes; - fill_holes->SetInputData(polydata); - fill_holes->SetHoleSize(hole_size); - fill_holes->Update(); - auto result = fill_holes->GetOutput(); - return CreateTriangleMeshFromVtkPolyData(result); -} - -std::tuple TriangleMesh::ComputeUVAtlas( - size_t size, - float gutter, - float max_stretch, - int parallel_partitions, - int nthreads) { - return kernel::uvunwrapping::ComputeUVAtlas(*this, size, size, gutter, - max_stretch, - parallel_partitions, nthreads); -} - -namespace { -/// Bakes vertex or triangle attributes to a texure. -/// -/// \tparam TAttr The data type of the attribute. -/// \tparam TInt The data type for triangle indices. -/// \tparam VERTEX_ATTR If true bake vertex attributes with interpolation. -/// If false bake triangle attributes. -/// -/// \param size The texture size. -/// \param margin The margin in pixels. -/// \param attr The vertex or triangle attribute tensor. -/// \param triangle_indices The triangle_indices of the TriangleMesh. -/// \param primitive_ids The primitive ids from ComputePrimitiveInfoTexture(). -/// \param primitive_uvs The primitive uvs from ComputePrimitiveInfoTexture(). -/// \param sqrdistance The squared distances from ComputePrimitiveInfoTexture(). -/// \param fill_value Fill value for the generated textures. -template -core::Tensor BakeAttribute(int size, - float margin, - const core::Tensor &attr, - const core::Tensor &triangle_indices, - const core::Tensor &primitive_ids, - const core::Tensor &primitive_uvs, - const core::Tensor &sqrdistance, - TAttr fill_value) { - core::SizeVector tex_shape({size, size}); - tex_shape.insert(tex_shape.end(), attr.GetShapeRef().begin() + 1, - attr.GetShapeRef().end()); - core::SizeVector components_shape(attr.GetShapeRef().begin() + 1, - attr.GetShapeRef().end()); - const int num_components = - components_shape.NumElements(); // is 1 for empty shape - core::Tensor tex = core::Tensor::Empty(tex_shape, attr.GetDtype()); - - const float threshold = (margin / size) * (margin / size); - Eigen::Map sqrdistance_map( - sqrdistance.GetDataPtr(), size, size); - Eigen::Map> tex_map( - tex.GetDataPtr(), num_components, size * size); - Eigen::Map> - tid_map(primitive_ids.GetDataPtr(), size, size); - Eigen::Map uv_map(primitive_uvs.GetDataPtr(), - 2, size * size); - Eigen::Map> - attr_map(attr.GetDataPtr(), num_components, - attr.GetLength()); - Eigen::Map> - triangle_indices_map(triangle_indices.GetDataPtr(), 3, - triangle_indices.GetLength()); - - for (int i = 0; i < size; ++i) { - for (int j = 0; j < size; ++j) { - const int64_t linear_idx = i * size + j; - if (sqrdistance_map(j, i) <= threshold) { - const uint32_t tid = tid_map(j, i); - if (VERTEX_ATTR) { - const auto &a = attr_map.col(triangle_indices_map(0, tid)); - const auto &b = attr_map.col(triangle_indices_map(1, tid)); - const auto &c = attr_map.col(triangle_indices_map(2, tid)); - TAttr u = uv_map(0, linear_idx); - TAttr v = uv_map(1, linear_idx); - tex_map.col(linear_idx) = - std::max(0, 1 - u - v) * a + u * b + v * c; - } else { - tex_map.col(linear_idx) = attr_map.col(tid); - } - } else { - tex_map.col(linear_idx).setConstant(fill_value); - } - } - } - - return tex; -} - -/// Computes textures with the primitive ids, primitive uvs, and the squared -/// distance to the closest primitive. -/// -/// This is a helper function for the texture baking functions. -/// -/// \param size The texture size. -/// \param primitive_ids The output tensor for the primitive ids. -/// \param primitive_uvs The output tensor for the primitive uvs. -/// \param sqrdistance The output tensor for the squared distances. -/// \param texture_uvs Input tensor with the texture uvs. -void ComputePrimitiveInfoTexture(int size, - core::Tensor &primitive_ids, - core::Tensor &primitive_uvs, - core::Tensor &sqrdistance, - const core::Tensor &texture_uvs) { - const int64_t num_triangles = texture_uvs.GetLength(); - - // Generate vertices for each triangle using (u,v,0) as position. - core::Tensor vertices({num_triangles * 3, 3}, core::Float32); - { - const float *uv_ptr = texture_uvs.GetDataPtr(); - float *v_ptr = vertices.GetDataPtr(); - for (int64_t i = 0; i < texture_uvs.GetLength(); ++i) { - for (int64_t j = 0; j < 3; ++j) { - v_ptr[i * 9 + j * 3 + 0] = uv_ptr[i * 6 + j * 2 + 0]; - v_ptr[i * 9 + j * 3 + 1] = uv_ptr[i * 6 + j * 2 + 1]; - v_ptr[i * 9 + j * 3 + 2] = 0; - } +bool TensorMap::IsSizeSynchronized() const { + const int64_t primary_size = GetPrimarySize(); + for (auto& kv : *this) { + if (kv.second.GetLength() != primary_size) { + return false; } } - core::Tensor triangle_indices = - core::Tensor::Empty({num_triangles, 3}, core::UInt32); - std::iota(triangle_indices.GetDataPtr(), - triangle_indices.GetDataPtr() + - triangle_indices.NumElements(), - 0); - - RaycastingScene scene; - scene.AddTriangles(vertices, triangle_indices); - - core::Tensor query_points = - core::Tensor::Empty({size, size, 3}, core::Float32); - float *ptr = query_points.GetDataPtr(); - for (int i = 0; i < size; ++i) { - float v = 1 - (i + 0.5f) / size; - for (int j = 0; j < size; ++j) { - float u = (j + 0.5f) / size; - ptr[i * size * 3 + j * 3 + 0] = u; - ptr[i * size * 3 + j * 3 + 1] = v; - ptr[i * size * 3 + j * 3 + 2] = 0; - } - } - - auto ans = scene.ComputeClosestPoints(query_points); - - Eigen::Map query_points_map( - query_points.GetDataPtr(), 3, size * size); - Eigen::Map closest_points_map( - ans["points"].GetDataPtr(), 3, size * size); - sqrdistance = core::Tensor::Empty({size, size}, core::Float32); - Eigen::Map sqrdistance_map(sqrdistance.GetDataPtr(), - size * size); - sqrdistance_map = - (closest_points_map - query_points_map).colwise().squaredNorm(); - primitive_ids = ans["primitive_ids"]; - primitive_uvs = ans["primitive_uvs"]; + return true; } -void UpdateMaterialTextures( - std::unordered_map &textures, - visualization::rendering::Material &material) { - for (auto &tex : textures) { - core::SizeVector element_shape(tex.second.GetShapeRef().begin() + 2, - tex.second.GetShapeRef().end()); - core::SizeVector shape(tex.second.GetShapeRef().begin(), - tex.second.GetShapeRef().begin() + 2); - if (tex.second.NumDims() > 2) { - shape.push_back(element_shape.NumElements()); - } - core::Tensor img_data = tex.second.Reshape(shape); - material.SetTextureMap(tex.first, Image(img_data)); +void TensorMap::AssertPrimaryKeyInMapOrEmpty() const { + if (size() != 0 && count(primary_key_) == 0) { + utility::LogError("TensorMap does not contain primary key \"{}\".", + primary_key_); } } -} // namespace -std::unordered_map -TriangleMesh::BakeVertexAttrTextures( - int size, - const std::unordered_set &vertex_attr, - double margin, - double fill, - bool update_material) { - if (!vertex_attr.size()) { - return std::unordered_map(); - } - if (!triangle_attr_.Contains("texture_uvs")) { - utility::LogError("Cannot find triangle attribute 'texture_uvs'"); - } - - core::Tensor texture_uvs = - triangle_attr_.at("texture_uvs").To(core::Device()).Contiguous(); - core::AssertTensorShape(texture_uvs, {core::None, 3, 2}); - core::AssertTensorDtype(texture_uvs, {core::Float32}); - - core::Tensor vertices({GetTriangleIndices().GetLength() * 3, 3}, - core::Float32); - { - float *uv_ptr = texture_uvs.GetDataPtr(); - float *v_ptr = vertices.GetDataPtr(); - for (int64_t i = 0; i < texture_uvs.GetLength(); ++i) { - for (int64_t j = 0; j < 3; ++j) { - v_ptr[i * 9 + j * 3 + 0] = uv_ptr[i * 6 + j * 2 + 0]; - v_ptr[i * 9 + j * 3 + 1] = uv_ptr[i * 6 + j * 2 + 1]; - v_ptr[i * 9 + j * 3 + 2] = 0; - } - } - } - core::Tensor triangle_indices = - core::Tensor::Empty(GetTriangleIndices().GetShape(), core::UInt32); - std::iota(triangle_indices.GetDataPtr(), - triangle_indices.GetDataPtr() + - triangle_indices.NumElements(), - 0); - - core::Tensor primitive_ids, primitive_uvs, sqrdistance; - ComputePrimitiveInfoTexture(size, primitive_ids, primitive_uvs, sqrdistance, - texture_uvs); - - std::unordered_map result; - for (auto attr : vertex_attr) { - if (!vertex_attr_.Contains(attr)) { - utility::LogError("Cannot find vertex attribute '{}'", attr); +void TensorMap::AssertNoReservedKeys() const { + const std::unordered_set& reserved_keys = GetReservedKeys(); + for (const auto& kv : *this) { + if (reserved_keys.count(kv.first)) { + utility::LogError("TensorMap contains reserved key \"{}\".", + kv.first); } - core::Tensor tensor = - vertex_attr_.at(attr).To(core::Device()).Contiguous(); - DISPATCH_FLOAT_INT_DTYPE_TO_TEMPLATE( - tensor.GetDtype(), GetTriangleIndices().GetDtype(), [&]() { - core::Tensor tex = BakeAttribute( - size, margin, tensor, GetTriangleIndices(), - primitive_ids, primitive_uvs, sqrdistance, - scalar_t(fill)); - result[attr] = tex; - }); } - if (update_material) { - UpdateMaterialTextures(result, this->GetMaterial()); - } - - return result; } -std::unordered_map -TriangleMesh::BakeTriangleAttrTextures( - int size, - const std::unordered_set &triangle_attr, - double margin, - double fill, - bool update_material) { - if (!triangle_attr.size()) { - return std::unordered_map(); - } - if (!triangle_attr_.Contains("texture_uvs")) { - utility::LogError("Cannot find triangle attribute 'texture_uvs'"); - } - - core::Tensor texture_uvs = - triangle_attr_.at("texture_uvs").To(core::Device()).Contiguous(); - core::AssertTensorShape(texture_uvs, {core::None, 3, 2}); - core::AssertTensorDtype(texture_uvs, {core::Float32}); - - core::Tensor primitive_ids, primitive_uvs, sqrdistance; - ComputePrimitiveInfoTexture(size, primitive_ids, primitive_uvs, sqrdistance, - texture_uvs); - - std::unordered_map result; - for (auto attr : triangle_attr) { - if (!triangle_attr_.Contains(attr)) { - utility::LogError("Cannot find triangle attribute '{}'", attr); - } - core::Tensor tensor = - triangle_attr_.at(attr).To(core::Device()).Contiguous(); - DISPATCH_DTYPE_TO_TEMPLATE(tensor.GetDtype(), [&]() { - core::Tensor tex; - if (GetTriangleIndices().GetDtype() == core::Int32) { - tex = BakeAttribute( - size, margin, tensor, GetTriangleIndices(), - primitive_ids, primitive_uvs, sqrdistance, - scalar_t(fill)); - } else if (GetTriangleIndices().GetDtype() == core::Int64) { - tex = BakeAttribute( - size, margin, tensor, GetTriangleIndices(), - primitive_ids, primitive_uvs, sqrdistance, - scalar_t(fill)); - } else { - utility::LogError("Unsupported triangle indices data type."); +void TensorMap::AssertSizeSynchronized() const { + if (!IsSizeSynchronized()) { + const int64_t primary_size = GetPrimarySize(); + std::stringstream ss; + ss << fmt::format("Primary Tensor \"{}\" has size {}, however: \n", + primary_key_, primary_size); + for (auto& kv : *this) { + if (kv.first != primary_key_ && + kv.second.GetLength() != primary_size) { + ss << fmt::format(" > Tensor \"{}\" has size {}.\n", + kv.first, kv.second.GetLength()); } - result[attr] = tex; - }); - } - if (update_material) { - UpdateMaterialTextures(result, this->GetMaterial()); - } - - return result; -} - -TriangleMesh TriangleMesh::ExtrudeRotation(double angle, - const core::Tensor &axis, - int resolution, - double translation, - bool capping) const { - using namespace vtkutils; - return ExtrudeRotationTriangleMesh(*this, angle, axis, resolution, - translation, capping); -} - -TriangleMesh TriangleMesh::ExtrudeLinear(const core::Tensor &vector, - double scale, - bool capping) const { - using namespace vtkutils; - return ExtrudeLinearTriangleMesh(*this, vector, scale, capping); -} - -int TriangleMesh::PCAPartition(int max_faces) { - core::Tensor verts = GetVertexPositions(); - core::Tensor tris = GetTriangleIndices(); - if (!tris.GetLength()) { - utility::LogError("Mesh must have at least one face."); - } - core::Tensor tris_centers = verts.IndexGet({tris}).Mean({1}); - - int num_parititions; - core::Tensor partition_ids; - std::tie(num_parititions, partition_ids) = - kernel::pcapartition::PCAPartition(tris_centers, max_faces); - SetTriangleAttr("partition_ids", partition_ids.To(GetDevice())); - return num_parititions; -} - -/// A helper to compute new vertex indices out of vertex mask. -/// \param tris_cpu CPU tensor with triangle indices to update. -/// \param vertex_mask CPU tensor with the mask for vertices. -template -static void UpdateTriangleIndicesByVertexMask(core::Tensor &tris_cpu, - const core::Tensor &vertex_mask) { - int64_t num_verts = vertex_mask.GetLength(); - int64_t num_tris = tris_cpu.GetLength(); - const T *vertex_mask_ptr = vertex_mask.GetDataPtr(); - std::vector prefix_sum(num_verts + 1, 0); - utility::InclusivePrefixSum(vertex_mask_ptr, vertex_mask_ptr + num_verts, - &prefix_sum[1]); - - // update triangle indices - T *vert_idx_ptr = tris_cpu.GetDataPtr(); - for (int64_t i = 0; i < num_tris * 3; ++i) { - vert_idx_ptr[i] = prefix_sum[vert_idx_ptr[i]]; - } -} - -/// A helper to copy mesh attributes. -/// \param dst destination mesh -/// \param src source mesh -/// \param vertex_mask vertex mask of the source mesh -/// \param tri_mask triangle mask of the source mesh -static void CopyAttributesByMasks(TriangleMesh &dst, - const TriangleMesh &src, - const core::Tensor &vertex_mask, - const core::Tensor &tri_mask) { - if (src.HasVertexPositions() && dst.HasVertexPositions()) { - for (auto item : src.GetVertexAttr()) { - if (!dst.HasVertexAttr(item.first)) { - dst.SetVertexAttr(item.first, - item.second.IndexGet({vertex_mask})); - } - } - } - - if (src.HasTriangleIndices() && dst.HasTriangleIndices()) { - for (auto item : src.GetTriangleAttr()) { - if (!dst.HasTriangleAttr(item.first)) { - dst.SetTriangleAttr(item.first, - item.second.IndexGet({tri_mask})); - } - } - } -} - -TriangleMesh TriangleMesh::SelectFacesByMask(const core::Tensor &mask) const { - if (!HasVertexPositions()) { - utility::LogWarning( - "[SelectFacesByMask] mesh has no vertex positions."); - return {}; - } - if (!HasTriangleIndices()) { - utility::LogWarning( - "[SelectFacesByMask] mesh has no triangle indices."); - return {}; - } - - core::AssertTensorShape(mask, {GetTriangleIndices().GetLength()}); - core::AssertTensorDtype(mask, core::Bool); - GetTriangleAttr().AssertSizeSynchronized(); - GetVertexAttr().AssertSizeSynchronized(); - - // select triangles - core::Tensor tris = GetTriangleIndices().IndexGet({mask}); - core::Tensor tris_cpu = tris.To(core::Device()).Contiguous(); - - // create mask for vertices that are part of the selected faces - const int64_t num_verts = GetVertexPositions().GetLength(); - // empty tensor to further construct the vertex mask - core::Tensor vertex_mask; - - DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tris_cpu.GetDtype(), tris, [&]() { - vertex_mask = core::Tensor::Zeros( - {num_verts}, core::Dtype::FromType()); - const int64_t num_tris = tris_cpu.GetLength(); - scalar_tris_t *vertex_mask_ptr = - vertex_mask.GetDataPtr(); - scalar_tris_t *vert_idx_ptr = tris_cpu.GetDataPtr(); - // mask for the vertices, which are used in the triangles - for (int64_t i = 0; i < num_tris * 3; ++i) { - vertex_mask_ptr[vert_idx_ptr[i]] = 1; } - UpdateTriangleIndicesByVertexMask(tris_cpu, vertex_mask); - }); - - tris = tris_cpu.To(GetDevice()); - vertex_mask = vertex_mask.To(GetDevice(), core::Bool); - core::Tensor verts = GetVertexPositions().IndexGet({vertex_mask}); - TriangleMesh result(verts, tris); - - CopyAttributesByMasks(result, *this, vertex_mask, mask); - - return result; -} - -/// brief Static negative checker for signed integer types -template ::value && - !std::is_same::value && - std::is_signed::value, - T>::type * = nullptr> -static bool IsNegative(T val) { - return val < 0; -} - -/// brief Overloaded static negative checker for unsigned integer types. -/// It unconditionally returns false, but we need it for template functions. -template ::value && - !std::is_same::value && - !std::is_signed::value, - T>::type * = nullptr> -static bool IsNegative(T val) { - return false; -} - -TriangleMesh TriangleMesh::SelectByIndex(const core::Tensor &indices) const { - core::AssertTensorShape(indices, {indices.GetLength()}); - if (indices.NumElements() == 0) { - return {}; - } - if (!HasVertexPositions()) { - utility::LogWarning("[SelectByIndex] TriangleMesh has no vertices."); - return {}; - } - GetVertexAttr().AssertSizeSynchronized(); - - // we allow indices of an integral type only - core::Dtype::DtypeCode indices_dtype_code = - indices.GetDtype().GetDtypeCode(); - if (indices_dtype_code != core::Dtype::DtypeCode::Int && - indices_dtype_code != core::Dtype::DtypeCode::UInt) { - utility::LogError( - "[SelectByIndex] indices are not of integral type {}.", - indices.GetDtype().ToString()); - } - core::Tensor indices_cpu = indices.To(core::Device()).Contiguous(); - core::Tensor tris_cpu, tri_mask; - core::Dtype tri_dtype; - if (HasTriangleIndices()) { - GetTriangleAttr().AssertSizeSynchronized(); - tris_cpu = GetTriangleIndices().To(core::Device()).Contiguous(); - // bool mask for triangles. - tri_mask = core::Tensor::Zeros({tris_cpu.GetLength()}, core::Bool); - tri_dtype = tris_cpu.GetDtype(); - } else { - utility::LogWarning("TriangleMesh has no triangle indices."); - tri_dtype = core::Int64; - } - - // int mask to select vertices for the new mesh. We need it as int as we - // will use its values to sum up and get the map of new indices - core::Tensor vertex_mask = - core::Tensor::Zeros({GetVertexPositions().GetLength()}, tri_dtype); - - DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() { - DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE( - indices_cpu.GetDtype(), indices, [&]() { - const int64_t num_tris = tris_cpu.GetLength(); - const int64_t num_verts = vertex_mask.GetLength(); - - // compute the vertices mask - scalar_tris_t *vertex_mask_ptr = - vertex_mask.GetDataPtr(); - const scalar_indices_t *indices_ptr = - indices_cpu.GetDataPtr(); - for (int64_t i = 0; i < indices.GetLength(); ++i) { - if (IsNegative(indices_ptr[i]) || - indices_ptr[i] >= - static_cast(num_verts)) { - utility::LogWarning( - "[SelectByIndex] indices contains index {} " - "out of range. " - "It is ignored.", - indices_ptr[i]); - continue; - } - vertex_mask_ptr[indices_ptr[i]] = 1; - } - - if (tri_mask.GetDtype() == core::Undefined) { - // we don't need to compute triangles, if there are none - return; - } - - // Build the triangle mask - scalar_tris_t *tris_cpu_ptr = - tris_cpu.GetDataPtr(); - bool *tri_mask_ptr = tri_mask.GetDataPtr(); - for (int64_t i = 0; i < num_tris; ++i) { - if (vertex_mask_ptr[tris_cpu_ptr[3 * i]] == 1 && - vertex_mask_ptr[tris_cpu_ptr[3 * i + 1]] == 1 && - vertex_mask_ptr[tris_cpu_ptr[3 * i + 2]] == 1) { - tri_mask_ptr[i] = true; - } - } - // select only needed triangles - tris_cpu = tris_cpu.IndexGet({tri_mask}); - // update the triangle indices - UpdateTriangleIndicesByVertexMask( - tris_cpu, vertex_mask); - }); - }); - - // send the vertex mask and triangle mask to original device and apply to - // vertices - vertex_mask = vertex_mask.To(GetDevice(), core::Bool); - if (tri_mask.NumElements() > 0) { // To() needs non-empty tensor - tri_mask = tri_mask.To(GetDevice()); - } - core::Tensor new_vertices = GetVertexPositions().IndexGet({vertex_mask}); - TriangleMesh result(GetDevice()); - result.SetVertexPositions(new_vertices); - if (tris_cpu.NumElements() > 0) { // To() needs non-empty tensor - result.SetTriangleIndices(tris_cpu.To(GetDevice())); - } - CopyAttributesByMasks(result, *this, vertex_mask, tri_mask); - - return result; -} - -TriangleMesh TriangleMesh::RemoveUnreferencedVertices() { - if (!HasVertexPositions() || GetVertexPositions().GetLength() == 0) { - utility::LogWarning( - "[RemoveUnreferencedVertices] TriangleMesh has no vertices."); - return *this; - } - GetVertexAttr().AssertSizeSynchronized(); - - core::Dtype tri_dtype = HasTriangleIndices() - ? GetTriangleIndices().GetDtype() - : core::Int64; - - int64_t num_verts_old = GetVertexPositions().GetLength(); - // int mask for vertices as we need to remap indices. - core::Tensor vertex_mask = core::Tensor::Zeros({num_verts_old}, tri_dtype); - - if (!HasTriangleIndices() || GetTriangleIndices().GetLength() == 0) { - utility::LogWarning( - "[RemoveUnreferencedVertices] TriangleMesh has no triangles. " - "Removing all vertices."); - // in this case we need to empty vertices and their attributes - } else { - GetTriangleAttr().AssertSizeSynchronized(); - core::Tensor tris_cpu = - GetTriangleIndices().To(core::Device()).Contiguous(); - DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() { - scalar_tris_t *tris_ptr = tris_cpu.GetDataPtr(); - scalar_tris_t *vertex_mask_ptr = - vertex_mask.GetDataPtr(); - for (int i = 0; i < tris_cpu.GetLength(); i++) { - vertex_mask_ptr[tris_ptr[3 * i]] = 1; - vertex_mask_ptr[tris_ptr[3 * i + 1]] = 1; - vertex_mask_ptr[tris_ptr[3 * i + 2]] = 1; - } - - UpdateTriangleIndicesByVertexMask(tris_cpu, - vertex_mask); - }); - SetTriangleIndices(tris_cpu.To(GetDevice())); - } - - // send the vertex mask to original device and apply to - // vertices - vertex_mask = vertex_mask.To(GetDevice(), core::Bool); - for (auto item : GetVertexAttr()) { - SetVertexAttr(item.first, item.second.IndexGet({vertex_mask})); - } - - utility::LogDebug( - "[RemoveUnreferencedVertices] {:d} vertices have been removed.", - (int)(num_verts_old - GetVertexPositions().GetLength())); - - return *this; -} - -template ::value && - !std::is_same::value, - T>::type * = nullptr> -using Edge = std::tuple; - -/// brief Helper function to get an edge with ordered vertex indices. -template -static inline Edge GetOrderedEdge(T vidx0, T vidx1) { - return (vidx0 < vidx1) ? Edge{vidx0, vidx1} : Edge{vidx1, vidx0}; -} - -/// brief Helper -/// -template -static std::unordered_map, - std::vector, - utility::hash_tuple>> -GetEdgeToTrianglesMap(const core::Tensor &tris_cpu) { - std::unordered_map, std::vector, - utility::hash_tuple>> - tris_per_edge; - auto AddEdge = [&](T vidx0, T vidx1, int64_t tidx) { - tris_per_edge[GetOrderedEdge(vidx0, vidx1)].push_back(tidx); + utility::LogError("{}", ss.str()); + } +} + +bool TensorMap::IsContiguous() const { + for (const auto& kv : *this) { + if (!kv.second.IsContiguous()) { + return false; + } + } + return true; +} + +TensorMap TensorMap::Contiguous() const { + TensorMap tensor_map_contiguous(GetPrimaryKey()); + for (const auto& kv : *this) { + // If the tensor is contiguous, the underlying memory is used. + tensor_map_contiguous[kv.first] = kv.second.Contiguous(); + } + return tensor_map_contiguous; +} + +std::unordered_set TensorMap::GetReservedKeys() { + const static std::unordered_set reserved_keys = { + // Python reserved key. + "__class__", + "__contains__", + "__delattr__", + "__delitem__", + "__dir__", + "__doc__", + "__eq__", + "__format__", + "__ge__", + "__getattribute__", + "__getitem__", + "__gt__", + "__hash__", + "__init__", + "__init_subclass__", + "__iter__", + "__le__", + "__len__", + "__lt__", + "__ne__", + "__new__", + "__reduce__", + "__reduce_ex__", + "__repr__", + "__reversed__", + "__setattr__", + "__setitem__", + "__sizeof__", + "__str__", + "__subclasshook__", + "clear", + "copy", + "fromkeys", + "get", + "items", + "keys", + "pop", + "popitem", + "setdefault", + "update", + "values", + // Custom reserved keys. + "primary_key", + "is_size_synchronized", + "assert_size_synchronized", }; - const T *tris_ptr = tris_cpu.GetDataPtr(); - for (int64_t tidx = 0; tidx < tris_cpu.GetLength(); ++tidx) { - const T *triangle = &tris_ptr[3 * tidx]; - AddEdge(triangle[0], triangle[1], tidx); - AddEdge(triangle[1], triangle[2], tidx); - AddEdge(triangle[2], triangle[0], tidx); - } - return tris_per_edge; + return reserved_keys; } -TriangleMesh TriangleMesh::RemoveNonManifoldEdges() { - if (!HasVertexPositions() || GetVertexPositions().GetLength() == 0) { - utility::LogWarning( - "[RemoveNonManifildEdges] TriangleMesh has no vertices."); - return *this; - } - - if (!HasTriangleIndices() || GetTriangleIndices().GetLength() == 0) { - utility::LogWarning( - "[RemoveNonManifoldEdges] TriangleMesh has no triangles."); - return *this; - } - - GetVertexAttr().AssertSizeSynchronized(); - GetTriangleAttr().AssertSizeSynchronized(); - - core::Tensor tris_cpu = - GetTriangleIndices().To(core::Device()).Contiguous(); - - if (!HasTriangleAttr("areas")) { - ComputeTriangleAreas(); - } - core::Tensor tri_areas_cpu = - GetTriangleAttr("areas").To(core::Device()).Contiguous(); - - DISPATCH_FLOAT_INT_DTYPE_TO_TEMPLATE( - GetVertexPositions().GetDtype(), tris_cpu.GetDtype(), [&]() { - scalar_t *tri_areas_ptr = tri_areas_cpu.GetDataPtr(); - auto edges_to_tris = GetEdgeToTrianglesMap(tris_cpu); - - // lambda to compare triangles areas by index - auto area_greater_compare = [&tri_areas_ptr](size_t lhs, - size_t rhs) { - return tri_areas_ptr[lhs] > tri_areas_ptr[rhs]; - }; - - // go through all edges and for those that have more than 2 - // triangles attached, remove the triangles with the minimal - // area - for (auto &kv : edges_to_tris) { - // remove all triangles which are already marked for removal - // (area < 0) note, the erasing of triangles happens - // afterwards - auto tris_end = std::remove_if( - kv.second.begin(), kv.second.end(), - [=](size_t t) { return tri_areas_ptr[t] < 0; }); - // count non-removed triangles (with area > 0). - int n_tris = std::distance(kv.second.begin(), tris_end); - - if (n_tris <= 2) { - // nothing to do here as either: - // - all triangles of the edge are already marked for - // deletion - // - the edge is manifold: it has 1 or 2 triangles with - // a non-negative area - continue; - } - - // now erase all triangle indices already marked for removal - kv.second.erase(tris_end, kv.second.end()); - - // find first to triangles with the maximal area - std::nth_element(kv.second.begin(), kv.second.begin() + 1, - kv.second.end(), area_greater_compare); - - // mark others for deletion - for (auto it = kv.second.begin() + 2; it < kv.second.end(); - ++it) { - tri_areas_ptr[*it] = -1; - } - } - }); - - // mask for triangles with positive area - core::Tensor tri_mask = tri_areas_cpu.Gt(0.0).To(GetDevice()); - - // pick up positive-area triangles (and their attributes) - for (auto item : GetTriangleAttr()) { - SetTriangleAttr(item.first, item.second.IndexGet({tri_mask})); - } - - return *this; -} - -core::Tensor TriangleMesh::GetNonManifoldEdges( - bool allow_boundary_edges /* = true */) const { - if (!HasVertexPositions()) { - utility::LogWarning( - "[GetNonManifoldEdges] TriangleMesh has no vertices."); - return {}; - } +std::string TensorMap::ToString() const { + const std::string primary_key = GetPrimaryKey(); - if (!HasTriangleIndices()) { - utility::LogWarning( - "[GetNonManifoldEdges] TriangleMesh has no triangles."); - return {}; + if (empty()) { + return fmt::format("TensorMap(primary_key=\"{}\") with no attribute", + primary_key); } - core::Tensor result; - core::Tensor tris_cpu = - GetTriangleIndices().To(core::Device()).Contiguous(); - core::Dtype tri_dtype = tris_cpu.GetDtype(); - - DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() { - auto edges = GetEdgeToTrianglesMap(tris_cpu); - std::vector non_manifold_edges; - - for (auto &kv : edges) { - if ((allow_boundary_edges && - (kv.second.size() < 1 || kv.second.size() > 2)) || - (!allow_boundary_edges && kv.second.size() != 2)) { - non_manifold_edges.push_back(std::get<0>(kv.first)); - non_manifold_edges.push_back(std::get<1>(kv.first)); - } + size_t max_key_len = 0; + bool has_primary_key = false; + std::vector keys; + keys.reserve(size()); + for (const auto& kv : *this) { + const std::string key = kv.first; + keys.push_back(key); + max_key_len = std::max(max_key_len, key.size()); + if (key == primary_key) { + has_primary_key = true; } - - result = core::Tensor(non_manifold_edges, - {(long int)non_manifold_edges.size() / 2, 2}, - tri_dtype, GetTriangleIndices().GetDevice()); - }); - - return result; -} - -PointCloud TriangleMesh::SamplePointsUniformly( - size_t number_of_points, bool use_triangle_normal /*=false*/) { - if (number_of_points <= 0) { - utility::LogError("number_of_points <= 0"); - } - if (IsEmpty()) { - utility::LogError("Input mesh is empty. Cannot sample points."); - } - if (!HasTriangleIndices()) { - utility::LogError("Input mesh has no triangles. Cannot sample points."); - } - if (use_triangle_normal && !HasTriangleNormals()) { - ComputeTriangleNormals(true); } - if (!HasTriangleAttr("areas")) { - ComputeTriangleAreas(); // Compute area of each triangle - } - if (!IsCPU()) { - utility::LogWarning( - "SamplePointsUniformly is implemented only on CPU. Computing " - "on CPU."); - } - bool use_vert_normals = HasVertexNormals() && !use_triangle_normal; - bool use_albedo = - HasTriangleAttr("texture_uvs") && GetMaterial().HasAlbedoMap(); - bool use_vert_colors = HasVertexColors() && !use_albedo; - - auto cpu = core::Device(); - core::Tensor null_tensor({0}, core::Float32); // zero size tensor - auto triangles = GetTriangleIndices().To(cpu).Contiguous(), - vertices = GetVertexPositions().To(cpu).Contiguous(); - auto float_dt = vertices.GetDtype(); - auto areas = GetTriangleAttr("areas").To(cpu, float_dt).Contiguous(), - vertex_normals = - use_vert_normals - ? GetVertexNormals().To(cpu, float_dt).Contiguous() - : null_tensor, - triangle_normals = - use_triangle_normal - ? GetTriangleNormals().To(cpu, float_dt).Contiguous() - : null_tensor, - vertex_colors = - use_vert_colors - ? GetVertexColors().To(cpu, core::Float32).Contiguous() - : null_tensor, - texture_uvs = use_albedo ? GetTriangleAttr("texture_uvs") - .To(cpu, float_dt) - .Contiguous() - : null_tensor, - // With correct range conversion [0,255] -> [0,1] - albedo = use_albedo ? GetMaterial() - .GetAlbedoMap() - .To(core::Float32) - .To(cpu) - .AsTensor() - : null_tensor; - if (use_vert_colors) { - if (GetVertexColors().GetDtype() == core::UInt8) vertex_colors /= 255; - if (GetVertexColors().GetDtype() == core::UInt16) - vertex_colors /= 65535; - } - - std::array result = - kernel::trianglemesh::SamplePointsUniformlyCPU( - triangles, vertices, areas, vertex_normals, vertex_colors, - triangle_normals, texture_uvs, albedo, number_of_points); + std::sort(keys.begin(), keys.end()); - PointCloud pcd(result[0]); - if (use_vert_normals || use_triangle_normal) pcd.SetPointNormals(result[1]); - if (use_albedo || use_vert_colors) pcd.SetPointColors(result[2]); - return pcd.To(GetDevice()); -} + const std::string tensor_format_str = fmt::format( + " - {{:<{}}}: shape={{}}, dtype={{}}, device={{}}", max_key_len); -core::Tensor TriangleMesh::ComputeMetrics(const TriangleMesh &mesh2, - std::vector metrics, - MetricParameters params) const { - if (IsEmpty() || mesh2.IsEmpty()) { - utility::LogError("One or both input triangle meshes are empty!"); - } - if (!IsCPU() || !mesh2.IsCPU()) { - utility::LogWarning( - "ComputeDistance is implemented only on CPU. Computing on " - "CPU."); + std::stringstream ss; + ss << fmt::format("TensorMap(primary_key=\"{}\") with {} attribute{}:", + primary_key, size(), size() > 1 ? "s" : "") + << std::endl; + for (const std::string& key : keys) { + const core::Tensor& val = at(key); + ss << fmt::format(tensor_format_str, key, val.GetShape().ToString(), + val.GetDtype().ToString(), + val.GetDevice().ToString()); + if (key == primary_key) { + ss << " (primary)"; + } + ss << std::endl; } - auto cpu_mesh1 = To(core::Device("CPU:0")), - cpu_mesh2 = mesh2.To(core::Device("CPU:0")); - core::Tensor points1 = - cpu_mesh1.SamplePointsUniformly(params.n_sampled_points) - .GetPointPositions(); - core::Tensor points2 = - cpu_mesh2.SamplePointsUniformly(params.n_sampled_points) - .GetPointPositions(); - - RaycastingScene scene1, scene2; - scene1.AddTriangles(cpu_mesh1); - scene2.AddTriangles(cpu_mesh2); - - core::Tensor distance21 = scene1.ComputeDistance(points2); - core::Tensor distance12 = scene2.ComputeDistance(points1); - return ComputeMetricsCommon(distance12, distance21, metrics, params); + const std::string example_key = has_primary_key ? primary_key : keys[0]; + ss << fmt::format(" (Use . to access attributes, e.g., tensor_map.{})", + example_key); + return ss.str(); } } // namespace geometry diff --git a/cpp/open3d/t/geometry/TriangleMesh.h b/cpp/open3d/t/geometry/TriangleMesh.h index 2b324751004..9dc2d66c469 100644 --- a/cpp/open3d/t/geometry/TriangleMesh.h +++ b/cpp/open3d/t/geometry/TriangleMesh.h @@ -7,1064 +7,210 @@ #pragma once -#include +#include #include +#include #include "open3d/core/Tensor.h" -#include "open3d/core/TensorCheck.h" -#include "open3d/geometry/TriangleMesh.h" -#include "open3d/t/geometry/BoundingVolume.h" -#include "open3d/t/geometry/DrawableGeometry.h" -#include "open3d/t/geometry/Geometry.h" -#include "open3d/t/geometry/TensorMap.h" -#include "open3d/visualization/rendering/Model.h" namespace open3d { namespace t { namespace geometry { -class LineSet; -class PointCloud; - -/// \class TriangleMesh -/// \brief A triangle mesh contains vertices and triangles. -/// -/// The triangle mesh class stores the attribute data in key-value maps. There -/// are two maps: the vertex attributes map, and the triangle attribute map. -/// -/// - Default attribute: vertex_attr_["positions"], triangle_attr_["indices"] -/// - Vertex positions -/// - TriangleMesh::GetVertexPositions() -/// - TriangleMesh::SetVertexPositions(const Tensor& vertex_positions) -/// - TriangleMesh::HasVertexPositions() -/// - Value tensor must have shape {num_vertices, 3}. -/// - Triangle indices -/// - TriangleMesh::GetTriangleIndices() -/// - TriangleMesh::SetTriangleIndices(const Tensor& triangle_indices) -/// - TriangleMesh::HasTriangleIndices() -/// - Value tensor must have shape {num_triangles, 3}. -/// - Created by default, required for all triangle meshes. -/// - The device of vertex positions and triangle indices must be the same. -/// They determine the device of the trianglemesh. -/// -/// - Common attributes: vertex_attr_["normals"], vertex_attr_["colors"] -/// triangle_attr_["normals"], triangle_attr_["colors"] -/// - Vertex normals -/// - TriangleMesh::GetVertexNormals() -/// - TriangleMesh::SetVertexNormals(const Tensor& vertex_normals) -/// - TriangleMesh::HasVertexNormals() -/// - Value tensor must have shape {num_vertices, 3}. -/// - Value tensor can have any dtype. -/// - Vertex colors -/// - TriangleMesh::GetVertexColors() -/// - TriangleMesh::SetVertexColors(const Tensor& vertex_colors) -/// - TriangleMesh::HasVertexColors() -/// - Value tensor must have shape {num_vertices, 3}. -/// - Value tensor can have any dtype. -/// - Triangle normals -/// - TriangleMesh::GetTriangleNormals() -/// - TriangleMesh::SetTriangleNormals(const Tensor& triangle_normals) -/// - TriangleMesh::HasTriangleNormals() -/// - Value tensor must have shape {num_triangles, 3}. -/// - Value tensor can have any dtype. -/// - Triangle colors -/// - TriangleMesh::GetTriangleColors() -/// - TriangleMesh::SetTriangleColors(const Tensor& triangle_colors) -/// - TriangleMesh::HasTriangleColors() -/// - Value tensor must have shape {num_triangles, 3}. -/// - Value tensor can have any dtype. -/// - Not created by default. -/// - For all attributes above, the device must be consistent with the -/// device of the triangle mesh. +/// TensorMap is a unordered_map with a primary key. It is +/// typically used as a container for geometry attributes. /// -/// - Custom attributes: e.g. vetex_attr_["labels"], triangle_attr_["labels"] -/// - Use generalized helper functions, e.g.: -/// - TriangleMesh::GetVertexAttr(const std::string& key) -/// - TriangleMesh::SetVertexAttr(const std::string& key, -/// const Tensor& value) -/// - TriangleMesh::HasVertexAttr(const std::string& key) -/// - TriangleMesh::GetTriangleAttr(const std::string& key) -/// - TriangleMesh::SetTriangleAttr(const std::string& key, -/// const Tensor& value) -/// - TriangleMesh::HasTriangleAttr(const std::string& key) -/// - Not created by default. Users can add their own custom attributes. -/// - Value tensor must be on the same device as the triangle mesh. +/// e.g. +/// tensor_map.primary_key: "positions" +/// tensor_map["positions"] : Tensor of shape {100, 3}. +/// tensor_map["colors"] : Tensor of shape {100, 3}. +/// tensor_map["normals"] : Tensor of shape {100, 3}. /// -/// Note that the we can also use the generalized helper functions for the -/// default and common attributes. -class TriangleMesh : public Geometry, public DrawableGeometry { -public: - /// Construct an empty trianglemesh on the provided device. - /// \param device The device on which to initialize the trianglemesh - /// (default: 'CPU:0'). - TriangleMesh(const core::Device &device = core::Device("CPU:0")); - - /// Construct a trianglemesh from vertices and triangles. - /// - /// The input tensors will be directly used as the underlying storage of - /// the triangle mesh (no memory copy). The device for \p vertex_positions - /// must be consistent with \p triangle_indices. - /// - /// \param vertex_positions A tensor with element shape {3}. - /// \param triangle_indices A tensor with element shape {3}. - TriangleMesh(const core::Tensor &vertex_positions, - const core::Tensor &triangle_indices); - - virtual ~TriangleMesh() override {} - +/// Typically, tensors in the TensorMap should have the same length (the first +/// dimension of shape) and device as the primary tensor. +class TensorMap : public std::unordered_map { public: - /// \brief Text description. - std::string ToString() const; - - /// Transfer the triangle mesh to a specified device. - /// \param device The targeted device to convert to. - /// \param copy If true, a new triangle mesh is always created; if false, - /// the copy is avoided when the original triangle mesh is already on the - /// targeted device. - TriangleMesh To(const core::Device &device, bool copy = false) const; - - /// Returns copy of the triangle mesh on the same device. - TriangleMesh Clone() const { return To(GetDevice(), /*copy=*/true); } - - /// Getter for vertex_attr_ TensorMap. Used in Pybind. - const TensorMap &GetVertexAttr() const { return vertex_attr_; } - - /// Getter for vertex_attr_ TensorMap. - TensorMap &GetVertexAttr() { return vertex_attr_; } - - /// Get vertex attributes in vertex_attr_. Throws exception if the attribute - /// does not exist. - /// - /// \param key Attribute name. - core::Tensor &GetVertexAttr(const std::string &key) { - return vertex_attr_.at(key); - } - - /// Get the value of the "positions" attribute in vertex_attr_. - /// Convenience function. - core::Tensor &GetVertexPositions() { return GetVertexAttr("positions"); } - - /// Get the value of the "colors" attribute in vertex_attr_. - /// Convenience function. - core::Tensor &GetVertexColors() { return GetVertexAttr("colors"); } - - /// Get the value of the "normals" attribute in vertex_attr_. - /// Convenience function. - core::Tensor &GetVertexNormals() { return GetVertexAttr("normals"); } - - /// Getter for triangle_attr_ TensorMap. Used in Pybind. - const TensorMap &GetTriangleAttr() const { return triangle_attr_; } - - /// Getter for triangle_attr_ TensorMap. - TensorMap &GetTriangleAttr() { return triangle_attr_; } - - /// Get triangle attributes in triangle_attr_. Throws exception if the - /// attribute does not exist. - /// - /// \param key Attribute name. - core::Tensor &GetTriangleAttr(const std::string &key) { - return triangle_attr_.at(key); - } - - /// Get the value of the "indices" attribute in triangle_attr_. - /// Convenience function. - core::Tensor &GetTriangleIndices() { return GetTriangleAttr("indices"); } - - /// Get the value of the "normals" attribute in triangle_attr_. - /// Convenience function. - core::Tensor &GetTriangleNormals() { return GetTriangleAttr("normals"); } - - /// Get the value of the "colors" attribute in triangle_attr_. - /// Convenience function. - core::Tensor &GetTriangleColors() { return GetTriangleAttr("colors"); } - - /// Get vertex attributes. Throws exception if the attribute does not exist. - /// - /// \param key Attribute name. - const core::Tensor &GetVertexAttr(const std::string &key) const { - return vertex_attr_.at(key); - } - - /// Removes vertex attribute by key value. Primary attribute "positions" - /// cannot be removed. Throws warning if attribute key does not exists. - /// - /// \param key Attribute name. - void RemoveVertexAttr(const std::string &key) { vertex_attr_.Erase(key); } - - /// Get the value of the "positions" attribute in vertex_attr_. - /// Convenience function. - const core::Tensor &GetVertexPositions() const { - return GetVertexAttr("positions"); - } - - /// Get the value of the "colors" attribute in vertex_attr_. - /// Convenience function. - const core::Tensor &GetVertexColors() const { - return GetVertexAttr("colors"); - } - - /// Get the value of the "normals" attribute in vertex_attr_. - /// Convenience function. - const core::Tensor &GetVertexNormals() const { - return GetVertexAttr("normals"); - } - - /// Get triangle attributes in triangle_attr_. Throws exception if the - /// attribute does not exist. - /// - /// \param key Attribute name. - const core::Tensor &GetTriangleAttr(const std::string &key) const { - return triangle_attr_.at(key); - } - - /// Removes triangle attribute by key value. Primary attribute "indices" - /// cannot be removed. Throws warning if attribute key does not exists. - /// - /// \param key Attribute name. - void RemoveTriangleAttr(const std::string &key) { - triangle_attr_.Erase(key); - } - - /// Get the value of the "indices" attribute in triangle_attr_. - /// Convenience function. - const core::Tensor &GetTriangleIndices() const { - return GetTriangleAttr("indices"); - } - - /// Get the value of the "normals" attribute in triangle_attr_. - /// Convenience function. - const core::Tensor &GetTriangleNormals() const { - return GetTriangleAttr("normals"); - } - - /// Get the value of the "colors" attribute in triangle_attr_. - /// Convenience function. - const core::Tensor &GetTriangleColors() const { - return GetTriangleAttr("colors"); - } - - /// Set vertex attributes. If the attribute key already exists, its value - /// will be overwritten, otherwise, the new key will be created. - /// - /// \param key Attribute name. - /// \param value A tensor. - void SetVertexAttr(const std::string &key, const core::Tensor &value) { - core::AssertTensorDevice(value, device_); - vertex_attr_[key] = value; - } - - /// Set the value of the "positions" attribute in vertex_attr_. - /// Convenience function. - void SetVertexPositions(const core::Tensor &value) { - core::AssertTensorShape(value, {utility::nullopt, 3}); - SetVertexAttr("positions", value); - } - - /// Set the value of the "colors" attribute in vertex_attr_. - /// Convenience function. - void SetVertexColors(const core::Tensor &value) { - core::AssertTensorShape(value, {utility::nullopt, 3}); - SetVertexAttr("colors", value); - } - - /// Set the value of the "normals" attribute in vertex_attr_. - /// This is a convenience function. - void SetVertexNormals(const core::Tensor &value) { - core::AssertTensorShape(value, {utility::nullopt, 3}); - SetVertexAttr("normals", value); - } - - /// Set triangle attributes. If the attribute key already exists, its value - /// will be overwritten, otherwise, the new key will be created. - /// - /// \param key Attribute name. - /// \param value A tensor. - void SetTriangleAttr(const std::string &key, const core::Tensor &value) { - core::AssertTensorDevice(value, device_); - triangle_attr_[key] = value; - } - - /// Set the value of the "indices" attribute in triangle_attr_. - void SetTriangleIndices(const core::Tensor &value) { - core::AssertTensorShape(value, {utility::nullopt, 3}); - SetTriangleAttr("indices", value); - } + /// Create empty TensorMap and set primary key. + explicit TensorMap(const std::string& primary_key) + : std::unordered_map(), + primary_key_(primary_key) { + AssertPrimaryKeyInMapOrEmpty(); + AssertNoReservedKeys(); + } + + /// A primary key is always required. This constructor can be marked as + /// delete in C++, but it is needed for pybind to bind as a generic python + /// map interface. + explicit TensorMap() : TensorMap("Undefined") { + utility::LogError("Please construct TensorMap with a primary key."); + } + + template + TensorMap(const std::string& primary_key, InputIt first, InputIt last) + : std::unordered_map(first, last), + primary_key_(primary_key) { + AssertPrimaryKeyInMapOrEmpty(); + AssertNoReservedKeys(); + } + + TensorMap(const std::string& primary_key, + const std::unordered_map& tensor_map) + : TensorMap(primary_key, tensor_map.begin(), tensor_map.end()) { + AssertPrimaryKeyInMapOrEmpty(); + AssertNoReservedKeys(); + } + + TensorMap(const std::string& primary_key, + std::initializer_list init) + : std::unordered_map(init), + primary_key_(primary_key) { + AssertPrimaryKeyInMapOrEmpty(); + AssertNoReservedKeys(); + } + + /// Copy constructor performs a "shallow" copy of the Tensors. + TensorMap(const TensorMap& other) + : std::unordered_map(other), + primary_key_(other.primary_key_) { + AssertPrimaryKeyInMapOrEmpty(); + AssertNoReservedKeys(); + } + + /// Move constructor performs a "shallow" copy of the Tensors. + TensorMap(TensorMap&& other) + : std::unordered_map(other), + primary_key_(other.primary_key_) { + AssertPrimaryKeyInMapOrEmpty(); + AssertNoReservedKeys(); + } + + /// \brief Erase elements for the TensorMap by key value, if the key + /// exists. If the key does not exists, a warning is thrown. + /// Also `primary_key` cannot be deleted. It is based on + /// `size_type unordered_map::erase(const key_type& k);`. + /// \return The number of elements deleted. [0 if key was not present]. + std::size_t Erase(const std::string key) { + if (key == primary_key_) { + utility::LogError("Primary key \"{}\" cannot be deleted.", + primary_key_); + } else if (!Contains(key)) { + utility::LogWarning("Key \"{}\" is not present.", key); + } + return this->erase(key); + } + + std::pair insert(const value_type& value) { + if (GetReservedKeys().count(value.first)) { + utility::LogError("Key \"{}\" is reserved.", value.first); + } + return std::unordered_map::insert(value); + } + + template + std::pair insert(P&& value) { + if (GetReservedKeys().count(value.first)) { + utility::LogError("Key \"{}\" is reserved.", value.first); + } + return std::unordered_map::insert( + std::forward

(value)); + } + + iterator insert(const_iterator hint, const value_type& value) { + if (GetReservedKeys().count(value.first)) { + utility::LogError("Key \"{}\" is reserved.", value.first); + } + return std::unordered_map::insert(hint, + value); + } + + template + iterator insert(const_iterator hint, P&& value) { + if (GetReservedKeys().count(value.first)) { + utility::LogError("Key \"{}\" is reserved.", value.first); + } + return std::unordered_map::insert( + hint, std::forward

(value)); + } - /// Set the value of the "normals" attribute in triangle_attr_. - /// This is a convenience function. - void SetTriangleNormals(const core::Tensor &value) { - core::AssertTensorShape(value, {utility::nullopt, 3}); - SetTriangleAttr("normals", value); + template + void insert(InputIt first, InputIt last) { + for (auto it = first; it != last; ++it) { + if (GetReservedKeys().count(it->first)) { + utility::LogError("Key \"{}\" is reserved.", it->first); + } + } + std::unordered_map::insert(first, last); } - /// Set the value of the "colors" attribute in triangle_attr_. - /// This is a convenience function. - void SetTriangleColors(const core::Tensor &value) { - core::AssertTensorShape(value, {utility::nullopt, 3}); - SetTriangleAttr("colors", value); + void insert(std::initializer_list ilist) { + for (auto it = ilist.begin(); it != ilist.end(); ++it) { + if (GetReservedKeys().count(it->first)) { + utility::LogError("Key \"{}\" is reserved.", it->first); + } + } + std::unordered_map::insert(ilist); } - /// Returns true if all of the following are true in vertex_attr_: - /// 1) attribute key exist - /// 2) attribute's length as vertices' length - /// 3) attribute's length > 0 - bool HasVertexAttr(const std::string &key) const { - return vertex_attr_.Contains(key) && - GetVertexAttr(key).GetLength() > 0 && - GetVertexAttr(key).GetLength() == - GetVertexPositions().GetLength(); - } - - /// Check if the "positions" attribute's value in vertex_attr_ has length > - /// 0. Convenience function. - bool HasVertexPositions() const { return HasVertexAttr("positions"); } + TensorMap& operator=(const TensorMap&) = default; - /// Returns true if all of the following are true in vertex_attr_: - /// 1) attribute "colors" exist - /// 2) attribute "colors"'s length as vertices' length - /// 3) attribute "colors"'s length > 0 - /// Convenience function. - bool HasVertexColors() const { return HasVertexAttr("colors"); } + TensorMap& operator=(TensorMap&&) = default; + + /// Returns the primary key of the TensorMap. + std::string GetPrimaryKey() const { return primary_key_; } - /// Returns true if all of the following are true in vertex_attr_: - /// 1) attribute "normals" exist - /// 2) attribute "normals"'s length as vertices' length - /// 3) attribute "normals"'s length > 0 - /// Convenience function. - bool HasVertexNormals() const { return HasVertexAttr("normals"); } - - /// Returns true if all of the following are true in triangle_attr_: - /// 1) attribute key exist - /// 2) attribute's length as triangles' length - /// 3) attribute's length > 0 - bool HasTriangleAttr(const std::string &key) const { - return triangle_attr_.Contains(key) && - GetTriangleAttr(key).GetLength() > 0 && - GetTriangleAttr(key).GetLength() == - GetTriangleIndices().GetLength(); + /// Returns a set with all keys. + std::unordered_set GetKeySet() const { + std::unordered_set keys; + for (const auto& item : *this) { + keys.insert(item.first); + } + return keys; } - /// Check if the "indices" attribute's value in triangle_attr_ has length - /// > 0. - /// Convenience function. - bool HasTriangleIndices() const { return HasTriangleAttr("indices"); } - - /// Returns true if all of the following are true in triangle_attr_: - /// 1) attribute "normals" exist - /// 2) attribute "normals"'s length as vertices' length - /// 3) attribute "normals"'s length > 0 - /// Convenience function. - bool HasTriangleNormals() const { return HasTriangleAttr("normals"); } - - /// Returns true if all of the following are true in triangle_attr_: - /// 1) attribute "colors" exist - /// 2) attribute "colors"'s length as vertices' length - /// 3) attribute "colors"'s length > 0 - /// Convenience function. - bool HasTriangleColors() const { return HasTriangleAttr("colors"); } - - /// Create a box triangle mesh. One vertex of the box will be placed at - /// the origin and the box aligns with the positive x, y, and z axes. - /// \param width is x-directional length. - /// \param height is y-directional length. - /// \param depth is z-directional length. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateBox( - double width = 1.0, - double height = 1.0, - double depth = 1.0, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Create a sphere triangle mesh. The sphere with radius will be centered - /// at (0, 0, 0). Its axis is aligned with z-axis. - /// \param radius defines the radius of the sphere. - /// \param resolution defines the resolution of the sphere. The longitudes - /// will be split into resolution segments (i.e. there are resolution + 1 - /// latitude lines including the north and south pole). The latitudes will - /// be split into `2 * resolution segments (i.e. there are 2 * resolution - /// longitude lines.) - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateSphere( - double radius = 1.0, - int resolution = 20, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); + /// Returns true if all tensors in the map have the same size. + bool IsSizeSynchronized() const; + + /// Assert IsSizeSynchronized(). + void AssertSizeSynchronized() const; - /// Create a tetrahedron triangle mesh. The centroid of the mesh will be - /// placed at (0, 0, 0) and the vertices have a distance of radius to the - /// center. - /// \param radius defines the distance from centroid to mesh vetices. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateTetrahedron( - double radius = 1.0, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); + /// Returns True if the underlying memory buffers of all the Tensors in the + /// TensorMap is contiguous. + bool IsContiguous() const; + + /// Returns a new contiguous TensorMap containing the same data in the same + /// device. For the contiguous tensors in the TensorMap, the same underlying + /// memory will be used. + TensorMap Contiguous() const; - /// Create a octahedron triangle mesh. The centroid of the mesh will be - /// placed at (0, 0, 0) and the vertices have a distance of radius to the - /// center. - /// \param radius defines the distance from centroid to mesh vetices. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateOctahedron( - double radius = 1.0, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); + /// Returns true if the key exists in the map. + /// Same as C++20's std::unordered_map::contains(). + bool Contains(const std::string& key) const { return count(key) != 0; } + + /// Get reserved keys for the map. A map cannot contain any of these keys. + static std::unordered_set GetReservedKeys(); - /// Create a icosahedron triangle mesh. The centroid of the mesh will be - /// placed at (0, 0, 0) and the vertices have a distance of radius to the - /// center. - /// \param radius defines the distance from centroid to mesh vetices. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateIcosahedron( - double radius = 1.0, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Create a cylinder triangle mesh. - /// \param radius defines the radius of the cylinder. - /// \param height defines the height of the cylinder. The axis of the - /// cylinder will be from (0, 0, -height/2) to (0, 0, height/2). - /// \param resolution defines the resolution of the cylinder. The circle - /// will be split into resolution segments - /// \param split defines the number of segments along the height direction. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateCylinder( - double radius = 1.0, - double height = 2.0, - int resolution = 20, - int split = 4, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Create a cone triangle mesh. - /// \param radius defines the radius of the cone. - /// \param height defines the height of the cone. The axis of the - /// cone will be from (0, 0, 0) to (0, 0, height). - /// \param resolution defines the resolution of the cone. The circle - /// will be split into resolution segments. - /// \param split defines the number of segments along the height direction. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateCone( - double radius = 1.0, - double height = 2.0, - int resolution = 20, - int split = 1, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Create a torus triangle mesh. - /// \param torus_radius defines the radius from the center of the - /// torus to the center of the tube. - /// \param tube_radius defines the radius of the torus tube. - /// \param radial_resolution defines the number of segments along the - /// radial direction. - /// \param tubular_resolution defines the number of segments along - /// the tubular direction. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateTorus( - double torus_radius = 1.0, - double tube_radius = 0.5, - int radial_resolution = 30, - int tubular_resolution = 20, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Create a arrow triangle mesh. - /// \param cylinder_radius defines the radius of the cylinder. - /// \param cone_radius defines the radius of the cone. - /// \param cylinder_height defines the height of the cylinder. The axis of - /// cylinder is from (0, 0, 0) to (0, 0, cylinder_height). - /// \param cone_height defines the height of the cone. The axis of the - /// cone will be from (0, 0, cylinder_height) to (0, 0, cylinder_height + - /// cone_height). \param resolution defines the resolution of the cone. The - /// circle will be split into resolution segments. \param cylinder_split - /// defines the number of segments along the cylinder_height direction. - /// \param cone_split defines the number of segments along - /// the cone_height direction. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateArrow( - double cylinder_radius = 1.0, - double cone_radius = 1.5, - double cylinder_height = 5.0, - double cone_height = 4.0, - int resolution = 20, - int cylinder_split = 4, - int cone_split = 1, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Create a coordinate frame mesh. - /// \param size defines the size of the coordinate frame. - /// \param origin defines the origin of the coordinate frame. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateCoordinateFrame( - double size = 1.0, - const Eigen::Vector3d &origin = Eigen::Vector3d(0.0, 0.0, 0.0), - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); + /// Print the TensorMap to string. + std::string ToString() const; - /// Create a Mobius strip. - /// \param length_split defines the number of segments along the Mobius - /// strip. - /// \param width_split defines the number of segments along the width - /// of the Mobius strip. - /// \param twists defines the number of twists of the strip. - /// \param radius defines the radius of the Mobius strip. - /// \param flatness controls the height of the strip. - /// \param width controls the width of the Mobius strip. - /// \param scale is used to scale the entire Mobius strip. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateMobius( - int length_split = 70, - int width_split = 15, - int twists = 1, - double radius = 1, - double flatness = 1, - double width = 1, - double scale = 1, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); +private: + /// Asserts that the map indeed contains the primary_key. This is typically + /// called in constructors. + void AssertPrimaryKeyInMapOrEmpty() const; - /// Create a text triangle mesh. - /// \param text The text for generating the mesh. ASCII characters 32-126 - /// are supported (includes alphanumeric characters and punctuation). In - /// addition the line feed '\n' is supported to start a new line. - /// \param depth The depth of the generated mesh. If depth is 0 then a flat - /// mesh will be generated. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in. - static TriangleMesh CreateText( - const std::string &text, - double depth = 0.0, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); + /// Asserts that there are no reserved keys in the map. This is typically + /// called in constructors or in modifying functions. + void AssertNoReservedKeys() const; - /// Create a mesh from a 3D scalar field (volume) by computing the - /// isosurface. This method uses the Flying Edges dual contouring method - /// that computes the isosurface similar to Marching Cubes. The center of - /// the first voxel of the volume is at the origin (0,0,0). The center of - /// the voxel at index [z,y,x] will be at (x,y,z). - /// \param volume 3D tensor with the volume. - /// \param contour_values A list of contour values at which isosurfaces will - /// be generated. The default value is 0. - /// \param device The device for the returned mesh. - static TriangleMesh CreateIsosurfaces( - const core::Tensor &volume, - const std::vector contour_values = {0.0}, - const core::Device &device = core::Device("CPU:0")); + /// Returns the size (length) of the primary key's tensor. + int64_t GetPrimarySize() const { return at(primary_key_).GetLength(); } -public: - /// Clear all data in the trianglemesh. - TriangleMesh &Clear() override { - vertex_attr_.clear(); - triangle_attr_.clear(); - return *this; + /// Returns the device of the primary key's tensor. + core::Device GetPrimaryDevice() const { + return at(primary_key_).GetDevice(); } - /// Returns !HasVertexPositions(), triangles are ignored. - bool IsEmpty() const override { return !HasVertexPositions(); } - - core::Tensor GetMinBound() const { return GetVertexPositions().Min({0}); } - - core::Tensor GetMaxBound() const { return GetVertexPositions().Max({0}); } - - core::Tensor GetCenter() const { return GetVertexPositions().Mean({0}); } - - /// \brief Transforms the VertexPositions, VertexNormals and TriangleNormals - /// (if exist) of the TriangleMesh. - /// - /// Transformation matrix is a 4x4 matrix. - /// T (4x4) = [[ R(3x3) t(3x1) ], - /// [ O(1x3) s(1x1) ]] - /// (s = 1 for Transformation without scaling) - /// - /// It applies the following general transform to each `positions` and - /// `normals`. - /// |x'| | R(0,0) R(0,1) R(0,2) t(0)| |x| - /// |y'| = | R(1,0) R(1,1) R(1,2) t(1)| @ |y| - /// |z'| | R(2,0) R(2,1) R(2,2) t(2)| |z| - /// |w'| | O(0,0) O(0,1) O(0,2) s | |1| - /// - /// [x, y, z] = [x', y', z'] / w' - /// - /// \param transformation Transformation [Tensor of dim {4,4}]. - /// \return Transformed TriangleMesh - TriangleMesh &Transform(const core::Tensor &transformation); - - /// \brief Translates the VertexPositions of the TriangleMesh. - /// \param translation translation tensor of dimension {3} - /// \param relative if true (default): translates relative to Center - /// \return Translated TriangleMesh - TriangleMesh &Translate(const core::Tensor &translation, - bool relative = true); - - /// \brief Scales the VertexPositions of the TriangleMesh. - /// \param scale Scale [double] of dimension - /// \param center Center [Tensor of dim {3}] about which the TriangleMesh is - /// to be scaled. - /// \return Scaled TriangleMesh - TriangleMesh &Scale(double scale, const core::Tensor ¢er); - - /// \brief Rotates the VertexPositions, VertexNormals and TriangleNormals - /// (if exists). - /// \param R Rotation [Tensor of dim {3,3}]. - /// \param center Center [Tensor of dim {3}] about which the TriangleMesh is - /// to be scaled. - /// \return Rotated TriangleMesh - TriangleMesh &Rotate(const core::Tensor &R, const core::Tensor ¢er); - - /// Normalize both triangle normals and vertex normals to length 1. - TriangleMesh &NormalizeNormals(); - - /// \brief Function to compute triangle normals, usually called before - /// rendering. - TriangleMesh &ComputeTriangleNormals(bool normalized = true); - - /// \brief Function to compute vertex normals, usually called before - /// rendering. - TriangleMesh &ComputeVertexNormals(bool normalized = true); - - /// \brief Function that computes the surface area of the mesh, i.e. the sum - /// of the individual triangle surfaces. - double GetSurfaceArea() const; - - /// \brief Function to compute triangle areas and save it as a triangle - /// attribute "areas". Prints a warning, if mesh is empty or has no - /// triangles. - TriangleMesh &ComputeTriangleAreas(); - - /// \brief Clip mesh with a plane. - /// This method clips the triangle mesh with the specified plane. - /// Parts of the mesh on the positive side of the plane will be kept and - /// triangles intersected by the plane will be cut. - /// \param point A point on the plane as [Tensor of dim {3}]. - /// \param normal The normal of the plane as [Tensor of dim {3}]. The normal - /// points to the positive side of the plane for which the geometry will be - /// kept. - /// \return New triangle mesh clipped with the plane. - TriangleMesh ClipPlane(const core::Tensor &point, - const core::Tensor &normal) const; - - /// \brief Extract contour slices given a plane. - /// This method extracts slices as LineSet from the mesh at specific - /// contour values defined by the specified plane. - /// \param point A point on the plane as [Tensor of dim {3}]. - /// \param normal The normal of the plane as [Tensor of dim {3}]. - /// \param contour_values Contour values at which slices will be generated. - /// The value describes the signed distance to the plane. - /// \return LineSet with the extracted contours. - LineSet SlicePlane(const core::Tensor &point, - const core::Tensor &normal, - const std::vector contour_values = {0.0}) const; - - core::Device GetDevice() const override { return device_; } - - /// Create a TriangleMesh from a legacy Open3D TriangleMesh. - /// \param mesh_legacy Legacy Open3D TriangleMesh. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in - /// (default CPU:0). - static geometry::TriangleMesh FromLegacy( - const open3d::geometry::TriangleMesh &mesh_legacy, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Convert to a legacy Open3D TriangleMesh. - open3d::geometry::TriangleMesh ToLegacy() const; - - /// Convert a TriangleMeshModel (e.g. as read from a file with - /// open3d::io::ReadTriangleMeshModel) to an unordered map of mesh names to - /// TriangleMeshes. Only one material is supported per mesh. Materials - /// common to multiple meshes will be dupicated. Textures (as - /// t::geometry::Image) will use shared storage. - /// \param model TriangleMeshModel to convert. - /// \param float_dtype Float32 or Float64, used to store floating point - /// values, e.g. vertices, normals, colors. - /// \param int_dtype Int32 or Int64, used to store index values, e.g. - /// triangles. - /// \param device The device where the resulting TriangleMesh resides in - /// (default CPU:0). Material textures use CPU storage - GPU resident - /// texture images are not yet supported. - /// \return unordered map of constituent mesh names to TriangleMeshes, with - /// materials. - static std::unordered_map - FromTriangleMeshModel( - const open3d::visualization::rendering::TriangleMeshModel &model, - core::Dtype float_dtype = core::Float32, - core::Dtype int_dtype = core::Int64, - const core::Device &device = core::Device("CPU:0")); - - /// Compute the convex hull of the triangle mesh using qhull. - /// - /// This runs on the CPU. - /// - /// \param joggle_inputs (default False). Handle precision problems by - /// randomly perturbing the input data. Set to True if perturbing the input - /// iis acceptable but you need convex simplicial output. If False, - /// neighboring facets may be merged in case of precision problems. See - /// [QHull docs](http://www.qhull.org/html/qh-impre.htm#joggle) for more - /// details. - /// - /// \return TriangleMesh representing the convexh hull. This contains an - /// extra vertex property "point_map" that contains the index of the - /// corresponding vertex in the original mesh. - TriangleMesh ComputeConvexHull(bool joggle_inputs = false) const; - - /// Function to simplify mesh using Quadric Error Metric Decimation by - /// Garland and Heckbert. - /// - /// This function always uses the CPU device. - /// - /// \param target_reduction The factor of triangles to delete, i.e., - /// setting this to 0.9 will return a mesh with about 10% of the original - /// triangle count. - /// It is not guaranteed that the target reduction factor will be reached. - /// \param preserve_volume If set to true this enables volume preservation - /// which reduces the error in triangle normal direction. - /// - /// \return Simplified TriangleMesh. - TriangleMesh SimplifyQuadricDecimation(double target_reduction, - bool preserve_volume = true) const; - - /// Computes the mesh that encompasses the union of the volumes of two - /// meshes. - /// Both meshes should be manifold. - /// - /// This function always uses the CPU device. - /// - /// \param mesh This is the second operand for the boolean operation. - /// \param tolerance Threshold which determines when point distances are - /// considered to be 0. - /// - /// \return The mesh describing the union volume. - TriangleMesh BooleanUnion(const TriangleMesh &mesh, - double tolerance = 1e-6) const; - - /// Computes the mesh that encompasses the intersection of the volumes of - /// two meshes. Both meshes should be manifold. - /// - /// This function always uses the CPU device. - /// - /// \param mesh This is the second operand for the boolean operation. - /// \param tolerance Threshold which determines when point distances are - /// considered to be 0. - /// - /// \return The mesh describing the intersection volume. - TriangleMesh BooleanIntersection(const TriangleMesh &mesh, - double tolerance = 1e-6) const; - - /// Computes the mesh that encompasses the volume after subtracting the - /// volume of the second operand. Both meshes should be manifold. - /// - /// This function always uses the CPU device. - /// - /// \param mesh This is the second operand for the boolean operation. - /// \param tolerance Threshold which determines when point distances are - /// considered to be 0. - /// - /// \return The mesh describing the difference volume. - TriangleMesh BooleanDifference(const TriangleMesh &mesh, - double tolerance = 1e-6) const; - - /// Create an axis-aligned bounding box from vertex attribute "positions". - AxisAlignedBoundingBox GetAxisAlignedBoundingBox() const; - - /// Create an oriented bounding box from vertex attribute "positions". - OrientedBoundingBox GetOrientedBoundingBox() const; - - /// Fill holes by triangulating boundary edges. - /// - /// This function always uses the CPU device. - /// - /// \param hole_size This is the approximate threshold for filling holes. - /// The value describes the maximum radius of holes to be filled. - /// - /// \return New mesh after filling holes. - TriangleMesh FillHoles(double hole_size = 1e6) const; - - /// Creates an UV atlas and adds it as triangle attr 'texture_uvs' to the - /// mesh. - /// - /// Input meshes must be manifold for this method to work. - /// - /// The algorithm is based on: - /// - Zhou et al, "Iso-charts: Stretch-driven Mesh Parameterization using - /// Spectral Analysis", Eurographics Symposium on Geometry Processing (2004) - /// - Sander et al. "Signal-Specialized Parametrization" Europgraphics 2002 - /// - /// This function always uses the CPU device. - /// - /// \param size The target size of the texture (size x size). The uv - /// coordinates will still be in the range [0..1] but parameters like gutter - /// use pixels as units. - /// \param gutter This is the space around the uv islands in pixels. - /// \param max_stretch The maximum amount of stretching allowed. The - /// parameter range is [0..1] with 0 meaning no stretch allowed. - /// \param parallel_partitions The approximate number of partitions created - /// before computing the UV atlas for parallelizing the computation. - /// Parallelization can be enabled with values > 1. Note that - /// parallelization increases the number of UV islands and can lead to - /// results with lower quality. - /// \param nthreads The number of threads used - /// when parallel_partitions is > 1. Set to 0 for automatic number of thread - /// detection. - /// - /// \return Tuple with (max stretch, num_charts, num_partitions) storing the - /// actual amount of stretch, the number of created charts, and the number - /// of parallel partitions created. - std::tuple ComputeUVAtlas(size_t size = 512, - float gutter = 1.0f, - float max_stretch = 1.f / 6, - int parallel_partitions = 1, - int nthreads = 0); - - /// Bake vertex attributes into textures. - /// - /// This function assumes a triangle attribute with name 'texture_uvs'. - /// Only float type attributes can be baked to textures. - /// - /// This function always uses the CPU device. - /// - /// \param size The width and height of the texture in pixels. Only square - /// textures are supported. - /// - /// \param vertex_attr The vertex attributes for which textures should be - /// generated. - /// - /// \param margin The margin in pixels. The recommended value is 2. The - /// margin are additional pixels around the UV islands to avoid - /// discontinuities. - /// - /// \param fill The value used for filling texels outside the UV islands. - /// - /// \param update_material If true updates the material of the mesh. - /// Baking a vertex attribute with the name 'albedo' will become the albedo - /// texture in the material. Existing textures in the material will be - /// overwritten. - /// - /// \return A dictionary of textures. - std::unordered_map BakeVertexAttrTextures( - int size, - const std::unordered_set &vertex_attr = {}, - double margin = 2., - double fill = 0., - bool update_material = true); - - /// Bake triangle attributes into textures. - /// - /// This function assumes a triangle attribute with name 'texture_uvs'. - /// - /// This function always uses the CPU device. - /// - /// \param size The width and height of the texture in pixels. Only square - /// textures are supported. - /// - /// \param vertex_attr The vertex attributes for which textures should be - /// generated. - /// - /// \param margin The margin in pixels. The recommended value is 2. The - /// margin are additional pixels around the UV islands to avoid - /// discontinuities. - /// - /// \param fill The value used for filling texels outside the UV islands. - /// - /// \param update_material If true updates the material of the mesh. - /// Baking a vertex attribute with the name 'albedo' will become the albedo - /// texture in the material. Existing textures in the material will be - /// overwritten. - /// - /// \return A dictionary of textures. - std::unordered_map BakeTriangleAttrTextures( - int size, - const std::unordered_set &triangle_attr = {}, - double margin = 2., - double fill = 0., - bool update_material = true); - - /// Sweeps the triangle mesh rotationally about an axis. - /// \param angle The rotation angle in degree. - /// \param axis The rotation axis. - /// \param resolution The resolution defines the number of intermediate - /// sweeps about the rotation axis. - /// \param translation The translation along the rotation axis. - /// \param capping If true adds caps to the mesh. - /// \return A triangle mesh with the result of the sweep operation. - TriangleMesh ExtrudeRotation(double angle, - const core::Tensor &axis, - int resolution = 16, - double translation = 0.0, - bool capping = true) const; - - /// Sweeps the triangle mesh along a direction vector. - /// \param vector The direction vector. - /// \param scale Scalar factor which essentially scales the direction - /// vector. \param capping If true adds caps to the mesh. \return A triangle - /// mesh with the result of the sweep operation. - TriangleMesh ExtrudeLinear(const core::Tensor &vector, - double scale = 1.0, - bool capping = true) const; - - /// Partition the mesh by recursively doing PCA. - /// This function creates a new triangle attribute with the name - /// "partition_ids". - /// \param max_faces The maximum allowed number of faces in a partition. - /// \return The number of partitions. - int PCAPartition(int max_faces); - - /// Returns a new mesh with the faces selected by a boolean mask. - /// \param mask A boolean mask with the shape (N) with N as the number of - /// faces in the mesh. - /// \return A new mesh with the selected faces. If the original mesh is - /// empty, return an empty mesh. - TriangleMesh SelectFacesByMask(const core::Tensor &mask) const; - - /// Returns a new mesh with the vertices selected by a vector of indices. - /// If an item from the indices list exceeds the max vertex number of - /// the mesh or has a negative value, it is ignored. - /// \param indices An integer list of indices. Duplicates are - /// allowed, but ignored. Signed and unsigned integral types are allowed. - /// \return A new mesh with the selected vertices and faces built - /// from the selected vertices. If the original mesh is empty, return - /// an empty mesh. - TriangleMesh SelectByIndex(const core::Tensor &indices) const; - - /// Removes unreferenced vertices from the mesh. - /// \return The reference to itself. - TriangleMesh RemoveUnreferencedVertices(); - - /// Removes all non-manifold edges, by successively deleting triangles - /// with the smallest surface area adjacent to the - /// non-manifold edge until the number of adjacent triangles to the edge is - /// `<= 2`. If mesh is empty or has no triangles, prints a warning and - /// returns immediately. \return The reference to itself. - TriangleMesh RemoveNonManifoldEdges(); - - /// Returns the non-manifold edges of the triangle mesh. - /// If \param allow_boundary_edges is set to false, then also boundary - /// edges are returned. - /// \return 2d integer tensor with shape {n,2} encoding ordered edges. - /// If mesh is empty or has no triangles, returns an empty tensor. - core::Tensor GetNonManifoldEdges(bool allow_boundary_edges = true) const; - - /// Sample points uniformly from the triangle mesh surface and return as a - /// PointCloud. Normals and colors are interpolated from the triangle mesh. - /// If texture_uvs and albedo are present, these are used to estimate the - /// sampled point color, otherwise vertex colors are used, if present. - /// During sampling, triangle areas are computed and saved in the "areas" - /// attribute. - /// \param number_of_points The number of points to sample. - /// \param use_triangle_normal If true, use the triangle normal as the - /// normal of the sampled point. Otherwise, interpolate the vertex normals. - PointCloud SamplePointsUniformly(size_t number_of_points, - bool use_triangle_normal = false); - - /// Compute various metrics between two triangle meshes. This uses ray - /// casting for distance computations between a sampled point cloud and a - /// triangle mesh. Currently, Chamfer distance, Hausdorff distance and - /// F-Score [[Knapitsch2017]] - /// are supported. The Chamfer distance is the sum of the mean distance to - /// the nearest neighbor from the sampled surface points of the first mesh - /// to the second mesh and vice versa. The F-Score at a fixed threshold - /// radius is the harmonic mean of the Precision and Recall. Recall is the - /// percentage of surface points from the first mesh that have the second - /// mesh within the threshold radius, while Precision is the percentage of - /// sampled points from the second mesh that have the first mesh surface - /// within the threhold radius. - - /// \f{eqnarray*}{ - /// \text{Chamfer Distance: } d_{CD}(X,Y) &=& \frac{1}{|X|}\sum_{i \in X} - /// || x_i - n(x_i, Y) || + \frac{1}{|Y|}\sum_{i \in Y} || y_i - n(y_i, X) - /// || \\{} - /// \text{Hausdorff distance: } d_H(X,Y) &=& \max \left\{ \max_{i \in X} - /// || x_i - n(x_i, Y) ||, \max_{i \in Y} || y_i - n(y_i, X) || \right\} - /// \\{} - /// \text{Precision: } P(X,Y|d) &=& \frac{100}{|X|} \sum_{i \in X} || x_i - /// - n(x_i, Y) || < d \\{} - /// \text{Recall: } R(X,Y|d) &=& \frac{100}{|Y|} \sum_{i \in Y} || y_i - - /// n(y_i, X) || < d \\{} - /// \text{F-Score: } F(X,Y|d) &=& \frac{2 P(X,Y|d) R(X,Y|d)}{P(X,Y|d) + - /// R(X,Y|d)} - /// \f} - - /// As a side effect, the triangle areas are saved in the "areas" attribute. - /// \param mesh2 Other point cloud to compare with. - /// \param metrics List of Metric s to compute. Multiple metrics can be - /// computed at once for efficiency. - /// \param params MetricParameters struct holds parameters required by - /// different metrics. - /// \returns Tensor containing the requested metrics. - core::Tensor ComputeMetrics( - const TriangleMesh &mesh2, - std::vector metrics = {Metric::ChamferDistance}, - MetricParameters params = MetricParameters()) const; - -protected: - core::Device device_ = core::Device("CPU:0"); - TensorMap vertex_attr_; - TensorMap triangle_attr_; + /// Primary key of the TensorMap. + std::string primary_key_; }; } // namespace geometry diff --git a/cpp/pybind/t/geometry/trianglemesh.cpp b/cpp/pybind/t/geometry/trianglemesh.cpp index 3f56bb52d8b..9c891430c91 100644 --- a/cpp/pybind/t/geometry/trianglemesh.cpp +++ b/cpp/pybind/t/geometry/trianglemesh.cpp @@ -734,11 +734,14 @@ This function always uses the CPU device. o3d.visualization.draw([{'name': 'difference', 'geometry': ans}]) )"); - + triangle_mesh.def("compute_adjacency_list", &TriangleMesh::ComputeAdjacencyList, + "Return Mesh Adjacency Matrix in CSR format using Triangle indices attribute."); + triangle_mesh.def("get_axis_aligned_bounding_box", &TriangleMesh::GetAxisAlignedBoundingBox, "Create an axis-aligned bounding box from vertex " "attribute 'positions'."); + triangle_mesh.def("get_oriented_bounding_box", &TriangleMesh::GetOrientedBoundingBox, "Create an oriented bounding box from vertex attribute " diff --git a/cpp/tests/t/geometry/TriangleMesh.cpp b/cpp/tests/t/geometry/TriangleMesh.cpp index 7bca9b4a55e..e9e42e1aa18 100644 --- a/cpp/tests/t/geometry/TriangleMesh.cpp +++ b/cpp/tests/t/geometry/TriangleMesh.cpp @@ -25,6 +25,70 @@ INSTANTIATE_TEST_SUITE_P(TriangleMesh, TriangleMeshPermuteDevices, testing::ValuesIn(PermuteDevices::TestCases())); +TEST_P(TriangleMeshPermuteDevices, ComputeAdjacencyList_emptyMesh) { +//Test the interface and the case when mesh is empty + + t::geometry::TriangleMesh empty_mesh; + + auto listCSR = empty_mesh.ComputeAdjacencyList(); + + core::Tensor adjacent_vertex = listCSR.first; + core::Tensor adjacent_index_start = listCSR.second; + EXPECT_TRUE(adjacent_vertex.GetLength() == 0); + EXPECT_TRUE(adjacent_index_start.GetLength() == 0); +} + +TEST_P(TriangleMeshPermuteDevices, ComputeAdjacencyList_matchValues) { +//Test the actual values computed in the function + + core::Device device = GetParam(); + core::Dtype float_dtype_custom = core::Float64; + core::Dtype int_dtype_custom = core::Int32; + + t::geometry::TriangleMesh mesh = + t::geometry::TriangleMesh::CreateTetrahedron( + 2, float_dtype_custom, int_dtype_custom, device); + + auto listCSR = mesh.ComputeAdjacencyList(); + core::Tensor adjv = listCSR.first; + core::Tensor adjst = listCSR.second; + + EXPECT_TRUE( adjv.GetLength() > 0); + EXPECT_TRUE( adjst.GetLength() > 0); + + core::Tensor csr_col = core::Tensor::Init( + {1, 2, 3, 0, 2, 3, 0, 1, 3, 0, 1, 2}, device); + + core::Tensor csr_row_idx = core::Tensor::Init( + {0, 3, 6, 9, 12}, device); + + EXPECT_EQ(adjv.GetLength(), csr_col.GetLength()); + EXPECT_EQ(adjst.GetLength(), csr_row_idx.GetLength()); + EXPECT_TRUE(adjv.AllEqual(csr_col)); + EXPECT_TRUE(adjst.AllEqual(csr_row_idx)); +} + +TEST_P(TriangleMeshPermuteDevices, ComputeAdjacencyList_expectedBehaviour) { +//On a larger mesh, test the interface and few expected properties + + core::Device device = GetParam(); + core::Dtype float_dtype_custom = core::Float64; + core::Dtype int_dtype_custom = core::Int32; + + t::geometry::TriangleMesh mesh = + t::geometry::TriangleMesh::CreateIcosahedron( + 2, float_dtype_custom, int_dtype_custom, device); + + + auto listCSR = mesh.ComputeAdjacencyList(); + core::Tensor adjv = listCSR.first; + core::Tensor adjst = listCSR.second; + + EXPECT_TRUE( adjv.GetLength() > 0); + EXPECT_TRUE( adjst.GetLength() > 0); + EXPECT_EQ(adjst.ToFlatVector()[adjst.GetLength()-1], adjv.GetLength()); +} + TEST_P(TriangleMeshPermuteDevices, DefaultConstructor) { t::geometry::TriangleMesh mesh; diff --git a/python/test/t/geometry/test_trianglemesh.py b/python/test/t/geometry/test_trianglemesh.py index 88678e3f877..d99072d6181 100644 --- a/python/test/t/geometry/test_trianglemesh.py +++ b/python/test/t/geometry/test_trianglemesh.py @@ -241,6 +241,66 @@ def test_create_octahedron(device): assert octahedron_custom.vertex.positions.allclose(vertex_positions_custom) assert octahedron_custom.triangle.indices.allclose(triangle_indices_custom) +@pytest.mark.parametrize("device", list_devices()) +def test_compute_adjacency_list(device): + # Test with custom parameters. + mesh = o3d.t.geometry.TriangleMesh.create_icosahedron( + 2, o3c.float64, o3c.int32, device) + adjv, adjst = mesh.compute_adjacency_list() + + # Check the number of edges at the end of row_index as per CRS format + assert adjst[-1] == len(adjv) + + num_vertices = len(adjst)-1 + # Create a adjacency matrix + adjacencyMatrix = np.zeros((num_vertices, num_vertices)) + + for s in range(num_vertices): + start = adjst[s].item() + end = adjst[s+1].item() + for v in range(start, end): + t = adjv[v].item() + adjacencyMatrix[s,t] = 1 + + # Number of edges + assert len(adjv) == adjacencyMatrix.sum() + + # Adjacency Matrix should be symmetric + assert np.array_equal(adjacencyMatrix, adjacencyMatrix.T) + + #Triangle faces from computed adjacency matrix should match + actual_indices = [ + [0, 1, 4], + [0, 1, 5], + [0, 4, 8], + [0, 5, 11], + [0, 8, 11], + [1, 4, 9], + [1, 5, 10], + [1, 9, 10], + [2, 3, 6], + [2, 3, 7], + [2, 6, 10], + [2, 7, 9], + [2, 9, 10], + [3, 6, 11], + [3, 7, 8], + [3, 8, 11], + [4, 7, 8], + [4, 7, 9], + [5, 6, 10], + [5, 6, 11] + ] + + computed_triangles = [] + for i in range(num_vertices): + for j in range(i+1, num_vertices): + for k in range(j+1, num_vertices): + if (adjacencyMatrix[i,j] + adjacencyMatrix[j,k] + adjacencyMatrix[k,i] == 3): + computed_triangles.append([i,j,k]) + + assert len(computed_triangles) == len(actual_indices) + assert np.array_equal(np.array(actual_indices,int), np.array(computed_triangles,int)) @pytest.mark.parametrize("device", list_devices()) def test_create_icosahedron(device):