Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RemoveDuplicatedTriangles feature using tensor API #6409

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions cpp/open3d/t/geometry/TriangleMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,81 @@ TriangleMesh &TriangleMesh::Rotate(const core::Tensor &R,
return *this;
}

TriangleMesh &TriangleMesh::RemoveDuplicatedTriangles() {
core::Tensor triangles = GetTriangleIndices();
int64_t num_triangles = triangles.GetLength();
if (num_triangles < 2) {
return *this;
}
PierreIntel marked this conversation as resolved.
Show resolved Hide resolved
// unordered_map to keep track of existing triangles
// hash function with code::Tensor not implemented so we use std::tuple
typedef std::tuple<int64_t, int64_t, int64_t> Index3;
std::unordered_map<Index3, size_t, utility::hash_tuple<Index3>>
triangle_to_old_index;
std::vector<int64_t> triangle_vec;

// Check if mesh has normals triangles and colors
bool has_tri_normal = HasTriangleNormals();
bool has_tri_colors = HasTriangleColors();
bool find_dup = false;

// mask of triangles to keep
core::Tensor mask_of_triangles =
core::Tensor::Empty({num_triangles}, core::Bool, GetDevice());

for (int64_t i = 0; i < num_triangles; i++) {
Index3 triangle_in_tuple;
// Extract vertices of the triangle
core::Tensor triangle_to_check = triangles[i];

if (triangles.GetDtype() == core::Int32) {
auto vertice_0 = triangle_to_check[0].Item<int32_t>();
auto vertice_1 = triangle_to_check[1].Item<int32_t>();
auto vertice_2 = triangle_to_check[2].Item<int32_t>();
triangle_vec = {vertice_0, vertice_1, vertice_2};
} else if (triangles.GetDtype() == core::Int64) {
auto vertice_0 = triangle_to_check[0].Item<int64_t>();
auto vertice_1 = triangle_to_check[1].Item<int64_t>();
auto vertice_2 = triangle_to_check[2].Item<int64_t>();
triangle_vec = {vertice_0, vertice_1, vertice_2};
}

// We first need to find the minimum index. Because triangle (0-1-2)
// and triangle (2-0-1) are the same.
std::sort(triangle_vec.begin(), triangle_vec.end());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Triangles / faces are directional, i.e. triangle {0,1,2} is different from triangle {2,1,0} since their surface normals will face in opposite directions. Instead of sort, we can use std::rotate(triangle_vec.begin(), ptr_to_min, triangle_vec.end()) This ensures direction is preserved.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, repeated memory allocation as we create a new vector for each face should be avoided.

triangle_in_tuple = std::make_tuple(triangle_vec[0], triangle_vec[1],
triangle_vec[2]);

// check is triangle is already present in the mesh
if (triangle_to_old_index.find(triangle_in_tuple) ==
triangle_to_old_index.end()) {
mask_of_triangles[i] = true;
triangle_to_old_index[triangle_in_tuple] = i;
} else {
find_dup = true;
mask_of_triangles[i] = false;
}
}

// do this only if there are some triangles to remove
if (find_dup) {
TriangleMesh mesh_without_dup = SelectFacesByMask(mask_of_triangles);
SetTriangleIndices(mesh_without_dup.GetTriangleIndices());
if (has_tri_normal) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you support other triangle attributes? (e.g. triangle colors, etc.) An important feature of the Tensor API data structures is support for generic triangle and vertex attributes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the triangle colors are manage in this : 3aa93dd

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to support arbitrary user defined attributes. Examples are labels, covariance, opacity, etc. See SelectFacesByMask for how to support arbitrary attributes.

core::Tensor tris =
GetTriangleNormals().IndexGet({mask_of_triangles});
SetTriangleNormals(tris);
}
if (has_tri_colors) {
core::Tensor colors =
GetTriangleColors().IndexGet({mask_of_triangles});
SetTriangleColors(colors);
}
}

return *this;
}

TriangleMesh &TriangleMesh::NormalizeNormals() {
if (HasVertexNormals()) {
SetVertexNormals(GetVertexNormals().Contiguous());
Expand Down
4 changes: 4 additions & 0 deletions cpp/open3d/t/geometry/TriangleMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,10 @@ class TriangleMesh : public Geometry, public DrawableGeometry {
/// \return Rotated TriangleMesh
TriangleMesh &Rotate(const core::Tensor &R, const core::Tensor &center);

/// \brief Remove duplicated triangles from the TriangleMesh
/// \return TriangleMesh without duplicated triangles
TriangleMesh &RemoveDuplicatedTriangles();

/// Normalize both triangle normals and vertex normals to length 1.
TriangleMesh &NormalizeNormals();

Expand Down
3 changes: 3 additions & 0 deletions cpp/pybind/t/geometry/trianglemesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ The attributes of the triangle mesh have different levels::
"Scale points.");
triangle_mesh.def("rotate", &TriangleMesh::Rotate, "R"_a, "center"_a,
"Rotate points and normals (if exist).");
triangle_mesh.def("remove_duplicated_triangles",
&TriangleMesh::RemoveDuplicatedTriangles,
"Remove duplicated triangles from a triangle");

triangle_mesh.def(
"normalize_normals", &TriangleMesh::NormalizeNormals,
Expand Down
84 changes: 84 additions & 0 deletions cpp/tests/t/geometry/TriangleMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,90 @@ TEST_P(TriangleMeshPermuteDevices, Rotate) {
core::Tensor::Init<float>({{2, 2, 1}, {2, 2, 1}}, device)));
}

TEST_P(TriangleMeshPermuteDevices, RemoveDuplicatedTriangles) {
core::Device device = GetParam();

t::geometry::TriangleMesh mesh(device);

mesh.SetVertexPositions(core::Tensor::Init<float>({{0, 0, 0},
{1, 0, 0},
{0, 0, 1},
{1, 0, 1},
{0, 1, 0},
{1, 1, 0},
{0, 1, 1},
{1, 1, 1}},
device));

mesh.SetTriangleIndices(core::Tensor::Init<int64_t>({{4, 7, 5},
{4, 6, 7},
{0, 2, 4},
{2, 6, 4},
{0, 1, 2},
{1, 3, 2},
{1, 5, 7},
{1, 7, 3},
{2, 3, 7},
{2, 7, 6},
{4, 6, 7},
{0, 4, 1},
{1, 4, 5}},
device));

mesh.SetTriangleNormals(core::Tensor::Init<float>({{4, 7, 5},
{4, 6, 7},
{0, 2, 4},
{2, 6, 4},
{0, 1, 2},
{1, 3, 2},
{1, 5, 7},
{1, 7, 3},
{2, 3, 7},
{2, 7, 6},
{4, 6, 7},
{0, 4, 1},
{1, 4, 5}},
device));

mesh.SetTriangleColors(core::Tensor::Ones({13, 3}, core::Float32, device));

mesh.RemoveDuplicatedTriangles();

EXPECT_EQ(mesh.GetTriangleIndices().GetLength(), 12);
EXPECT_EQ(mesh.GetTriangleNormals().GetLength(), 12);
EXPECT_EQ(mesh.GetTriangleColors().GetLength(), 12);

EXPECT_TRUE(mesh.GetTriangleIndices().AllClose(
core::Tensor::Init<int64_t>({{4, 7, 5},
{4, 6, 7},
{0, 2, 4},
{2, 6, 4},
{0, 1, 2},
{1, 3, 2},
{1, 5, 7},
{1, 7, 3},
{2, 3, 7},
{2, 7, 6},
{0, 4, 1},
{1, 4, 5}},
device)));

EXPECT_TRUE(mesh.GetTriangleNormals().AllClose(
core::Tensor::Init<float>({{4, 7, 5},
{4, 6, 7},
{0, 2, 4},
{2, 6, 4},
{0, 1, 2},
{1, 3, 2},
{1, 5, 7},
{1, 7, 3},
{2, 3, 7},
{2, 7, 6},
{0, 4, 1},
{1, 4, 5}},
device)));
}

TEST_P(TriangleMeshPermuteDevices, NormalizeNormals) {
core::Device device = GetParam();

Expand Down
26 changes: 26 additions & 0 deletions python/test/t/geometry/test_trianglemesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,3 +417,29 @@ def test_pickle(device):
mesh.vertex.positions.cpu().numpy())
np.testing.assert_equal(mesh_load.triangle.indices.cpu().numpy(),
mesh.triangle.indices.cpu().numpy())


@pytest.mark.parametrize("device", list_devices())
def test_remove_duplicated_triangles(device):

mesh = o3d.t.geometry.TriangleMesh()
vertex_positions = o3c.Tensor(
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 1.0],
[0.0, 1.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
o3c.float32, device)
triangle_indices = o3c.Tensor(
[[4, 7, 5], [4, 6, 7], [0, 2, 4], [2, 6, 4], [0, 1, 2], [1, 3, 2],
[1, 5, 7], [1, 7, 3], [2, 3, 7], [2, 7, 6], [4, 6, 7], [0, 4, 1],
[1, 4, 5]], o3c.int64, device)
mesh.vertex.positions = vertex_positions
mesh.triangle.indices = triangle_indices

mesh.remove_duplicated_triangles()

assert mesh.triangle.indices.shape == (12, 3)
np.testing.assert_allclose(
mesh.triangle.indices.cpu().numpy(),
o3c.Tensor(
[[4, 7, 5], [4, 6, 7], [0, 2, 4], [2, 6, 4], [0, 1, 2], [1, 3, 2],
[1, 5, 7], [1, 7, 3], [2, 3, 7], [2, 7, 6], [0, 4, 1], [1, 4, 5]],
o3c.int64, device).cpu().numpy())
Loading