Skip to content

Commit eee30b6

Browse files
committed
Implement t::geometry::TriangleMesh::RemoveUnreferencedVertices
The algorithm mimics the one in geometry::TriangleMesh::RemoveUnreferencedVertices. We first build a mask of vertices and then update all vertex attributes by that mask. Triangles are left untouched.
1 parent 4214a0d commit eee30b6

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

cpp/open3d/t/geometry/TriangleMesh.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,54 @@ TriangleMesh TriangleMesh::SelectByIndex(const core::Tensor &indices) const {
12521252
return result;
12531253
}
12541254

1255+
TriangleMesh TriangleMesh::RemoveUnreferencedVertices() {
1256+
if (!HasVertexPositions()) {
1257+
utility::LogWarning(
1258+
"[RemoveunreferencedVertices] TriangleMesh has no vertices.");
1259+
return {};
1260+
}
1261+
if (!HasTriangleIndices()) {
1262+
utility::LogWarning(
1263+
"[RemoveUnreferencedVertices] TriangleMesh has no triangles.");
1264+
return {};
1265+
}
1266+
GetVertexAttr().AssertSizeSynchronized();
1267+
GetTriangleAttr().AssertSizeSynchronized();
1268+
core::Tensor tris_cpu =
1269+
GetTriangleIndices().To(core::Device()).Contiguous();
1270+
// bool mask for triangles.
1271+
core::Tensor tri_mask =
1272+
core::Tensor::Zeros({tris_cpu.GetLength()}, core::Bool);
1273+
core::Dtype tri_dtype = tris_cpu.GetDtype();
1274+
1275+
// int mask for vertices as we need to remap indices.
1276+
core::Tensor vertex_mask =
1277+
core::Tensor::Zeros({GetVertexPositions().GetLength()}, tri_dtype);
1278+
1279+
DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() {
1280+
scalar_tris_t *tris_ptr = tris_cpu.GetDataPtr<scalar_tris_t>();
1281+
scalar_tris_t *vertex_mask_ptr =
1282+
vertex_mask.GetDataPtr<scalar_tris_t>();
1283+
for (int i = 0; i < tris_cpu.GetLength(); i++) {
1284+
vertex_mask_ptr[tris_ptr[3 * i]] = 1;
1285+
vertex_mask_ptr[tris_ptr[3 * i + 1]] = 1;
1286+
vertex_mask_ptr[tris_ptr[3 * i + 2]] = 1;
1287+
}
1288+
1289+
UpdateTriangleIndicesByVertexMask<scalar_tris_t>(tris_cpu, vertex_mask);
1290+
});
1291+
1292+
// Send the vertex mask and triangle mask to original device and apply to
1293+
// vertices. Vertex mask is non-empty, as there is at least one triangle.
1294+
vertex_mask = vertex_mask.To(GetDevice(), core::Bool);
1295+
1296+
for (auto item : GetVertexAttr()) {
1297+
SetVertexAttr(item.first, item.second.IndexGet({vertex_mask}));
1298+
}
1299+
1300+
return *this;
1301+
}
1302+
12551303
} // namespace geometry
12561304
} // namespace t
12571305
} // namespace open3d

cpp/open3d/t/geometry/TriangleMesh.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,10 @@ class TriangleMesh : public Geometry, public DrawableGeometry {
945945
/// an empty mesh.
946946
TriangleMesh SelectByIndex(const core::Tensor &indices) const;
947947

948+
/// Removes unreferenced vertices from the mesh.
949+
/// \return The reference to itself.
950+
TriangleMesh RemoveUnreferencedVertices();
951+
948952
protected:
949953
core::Device device_ = core::Device("CPU:0");
950954
TensorMap vertex_attr_;

cpp/pybind/t/geometry/trianglemesh.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,10 @@ or has a negative value, it is ignored.
962962
box = o3d.t.geometry.TriangleMesh.create_box()
963963
top_face = box.select_by_index([2, 3, 6, 7])
964964
)");
965+
966+
triangle_mesh.def("remove_unreferenced_vertices",
967+
&TriangleMesh::RemoveUnreferencedVertices,
968+
"Removes unreferenced vertices from the mesh in-place.");
965969
}
966970

967971
} // namespace geometry

cpp/tests/t/geometry/TriangleMesh.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,5 +1212,127 @@ TEST_P(TriangleMeshPermuteDevices, SelectByIndex) {
12121212
box_untouched.GetTriangleIndices()));
12131213
}
12141214

1215+
TEST_P(TriangleMeshPermuteDevices, RemoveUnreferencedVertices) {
1216+
t::geometry::TriangleMesh mesh_empty;
1217+
1218+
// check completely empty mesh
1219+
EXPECT_TRUE(mesh_empty.RemoveUnreferencedVertices().IsEmpty());
1220+
1221+
// check mesh w/o triangles
1222+
core::Tensor vertices_no_tris_orig =
1223+
core::Tensor::Ones({2, 3}, core::Float32, mesh_empty.GetDevice());
1224+
mesh_empty.SetVertexPositions(vertices_no_tris_orig);
1225+
EXPECT_TRUE(mesh_empty.RemoveUnreferencedVertices().IsEmpty());
1226+
1227+
// Torus
1228+
core::Tensor verts = core::Tensor::Init<double>({
1229+
{0, 0, 0}, /* 0 */
1230+
{3.0, 0.0, 0.0},
1231+
{1.5, 0.0, 0.866025},
1232+
{1, 2, 3}, /* 3 */
1233+
{1.5, 0.0, -0.866025},
1234+
{1.5, 2.59808, 0.0},
1235+
{0.75, 1.29904, 0.866025},
1236+
{0.75, 1.29904, -0.866025},
1237+
{-1.5, 2.59808, 0},
1238+
{-0.75, 1.29904, 0.866025},
1239+
{-0.75, 1.29904, -0.866025},
1240+
{-3.0, 0.0, 0.0},
1241+
{-1.5, 0.0, 0.866025},
1242+
{-1.5, 0.0, -0.866025},
1243+
{-1.5, -2.59808, 0.0},
1244+
{-0.75, -1.29904, 0.866025},
1245+
{-0.75, -1.29904, -0.866025},
1246+
{4, 5, 6}, /* 17 */
1247+
{1.5, -2.59808, 0.0},
1248+
{0.75, -1.29904, 0.866025},
1249+
{0.75, -1.29904, -0.866025},
1250+
{7, 8, 9} /* 21 */
1251+
});
1252+
1253+
core::Tensor tris = core::Tensor::Init<int32_t>(
1254+
{{5, 6, 1}, {1, 6, 2}, {6, 7, 2}, {2, 7, 4},
1255+
{7, 5, 4}, {4, 5, 1}, {8, 9, 5}, {5, 9, 6},
1256+
{9, 10, 6}, {6, 10, 7}, {10, 8, 7}, {7, 8, 5},
1257+
{11, 12, 8}, {8, 12, 9}, {12, 13, 9}, {9, 13, 10},
1258+
{13, 11, 10}, {10, 11, 8}, {14, 15, 11}, {11, 15, 12},
1259+
{15, 16, 12}, {12, 16, 13}, {16, 14, 13}, {13, 14, 11},
1260+
{18, 19, 14}, {14, 19, 15}, {19, 20, 15}, {15, 20, 16},
1261+
{20, 18, 16}, {16, 18, 14}, {1, 2, 18}, {18, 2, 19},
1262+
{2, 4, 19}, {19, 4, 20}, {4, 1, 20}, {20, 1, 18}});
1263+
t::geometry::TriangleMesh torus{verts, tris};
1264+
core::Tensor vertex_colors = core::Tensor::Init<float>(
1265+
{{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, {2.0, 2.0, 2.0},
1266+
{3.0, 3.0, 3.0}, {4.0, 4.0, 4.0}, {5.0, 5.0, 5.0},
1267+
{6.0, 6.0, 6.0}, {7.0, 7.0, 7.0}, {8.0, 8.0, 8.0},
1268+
{9.0, 9.0, 9.0}, {10.0, 10.0, 10.0}, {11.0, 11.0, 11.0},
1269+
{12.0, 12.0, 12.0}, {13.0, 13.0, 13.0}, {14.0, 14.0, 14.0},
1270+
{15.0, 15.0, 15.0}, {16.0, 16.0, 16.0}, {17.0, 17.0, 17.0},
1271+
{18.0, 18.0, 18.0}, {19.0, 19.0, 19.0}, {20.0, 20.0, 20.0},
1272+
{21.0, 21.0, 21.0}});
1273+
core::Tensor vertex_labels =
1274+
core::Tensor::Init<float>(
1275+
{{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, {2.0, 2.0, 2.0},
1276+
{3.0, 3.0, 3.0}, {4.0, 4.0, 4.0}, {5.0, 5.0, 5.0},
1277+
{6.0, 6.0, 6.0}, {7.0, 7.0, 7.0}, {8.0, 8.0, 8.0},
1278+
{9.0, 9.0, 9.0}, {10.0, 10.0, 10.0}, {11.0, 11.0, 11.0},
1279+
{12.0, 12.0, 12.0}, {13.0, 13.0, 13.0}, {14.0, 14.0, 14.0},
1280+
{15.0, 15.0, 15.0}, {16.0, 16.0, 16.0}, {17.0, 17.0, 17.0},
1281+
{18.0, 18.0, 18.0}, {19.0, 19.0, 19.0}, {20.0, 20.0, 20.0},
1282+
{21.0, 21.0, 21.0}}) *
1283+
10;
1284+
1285+
core::Tensor triangle_labels =
1286+
core::Tensor::Init<float>(
1287+
{{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0},
1288+
{2.0, 2.0, 2.0}, {3.0, 3.0, 3.0},
1289+
{4.0, 4.0, 4.0}, {5.0, 5.0, 5.0},
1290+
{6.0, 6.0, 6.0}, {7.0, 7.0, 7.0},
1291+
{8.0, 8.0, 8.0}, {9.0, 9.0, 9.0},
1292+
{10.0, 10.0, 10.0}, {11.0, 11.0, 11.0},
1293+
{12.0, 12.0, 12.0}, {13.0, 13.0, 13.0},
1294+
{14.0, 14.0, 14.0}, {15.0, 15.0, 15.0},
1295+
{16.0, 16.0, 16.0}, {17.0, 17.0, 17.0},
1296+
{18.0, 18.0, 18.0}, {19.0, 19.0, 19.0},
1297+
{20.0, 20.0, 20.0}, {21.0, 21.0, 21.0},
1298+
{22.0, 22.0, 22.0}, {23.0, 23.0, 23.0},
1299+
{24.0, 24.0, 24.0}, {25.0, 25.0, 25.0},
1300+
{26.0, 26.0, 26.0}, {27.0, 27.0, 27.0},
1301+
{28.0, 28.0, 28.0}, {29.0, 29.0, 29.0},
1302+
{30.0, 30.0, 30.0}, {31.0, 31.0, 31.0},
1303+
{32.0, 32.0, 32.0}, {33.0, 33.0, 33.0},
1304+
{34.0, 34.0, 34.0}, {35.0, 35.0, 35.0}}) *
1305+
100;
1306+
torus.SetVertexColors(vertex_colors);
1307+
torus.SetVertexAttr("labels", vertex_labels);
1308+
torus.ComputeVertexNormals();
1309+
torus.ComputeTriangleNormals();
1310+
1311+
// set the expected value
1312+
core::Tensor verts_mask = core::Tensor::Init<bool>(
1313+
{0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0});
1314+
core::Tensor expected_verts =
1315+
torus.GetVertexPositions().IndexGet({verts_mask});
1316+
core::Tensor expected_tris = t::geometry::TriangleMesh::CreateTorus(
1317+
2, 1, 6, 3, core::Float32, core::Int32)
1318+
.GetTriangleIndices();
1319+
core::Tensor expected_vert_normals =
1320+
torus.GetVertexNormals().IndexGet({verts_mask});
1321+
core::Tensor expected_tri_normals = torus.GetTriangleNormals().Clone();
1322+
core::Tensor expected_vert_labels =
1323+
torus.GetVertexAttr("labels").IndexGet({verts_mask});
1324+
core::Tensor expected_vert_colors =
1325+
torus.GetVertexAttr("colors").IndexGet({verts_mask});
1326+
1327+
torus.RemoveUnreferencedVertices();
1328+
1329+
EXPECT_TRUE(torus.GetVertexPositions().AllClose(expected_verts));
1330+
EXPECT_TRUE(torus.GetVertexNormals().AllClose(expected_vert_normals));
1331+
EXPECT_TRUE(torus.GetVertexColors().AllClose(expected_vert_colors));
1332+
EXPECT_TRUE(torus.GetVertexAttr("labels").AllClose(expected_vert_labels));
1333+
EXPECT_TRUE(torus.GetTriangleIndices().AllClose(expected_tris));
1334+
EXPECT_TRUE(torus.GetTriangleNormals().AllClose(expected_tri_normals));
1335+
}
1336+
12151337
} // namespace tests
12161338
} // namespace open3d

python/test/t/geometry/test_trianglemesh.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,52 @@ def test_select_by_index_64(device):
660660
untouched_sphere.vertex.positions)
661661
assert sphere_custom.triangle.indices.allclose(
662662
untouched_sphere.triangle.indices)
663+
664+
665+
def check_no_unreferenced_vertices(device, int_t, float_t):
666+
sphere = o3d.t.geometry.TriangleMesh.create_sphere(1, 3, float_t, int_t,
667+
device)
668+
expected_sphere = o3d.t.geometry.TriangleMesh.create_sphere(
669+
1, 3, float_t, int_t, device)
670+
671+
sphere.remove_unreferenced_vertices()
672+
673+
# nothing should be removed
674+
assert sphere.vertex.positions.allclose(expected_sphere.vertex.positions)
675+
assert sphere.triangle.indices.allclose(expected_sphere.triangle.indices)
676+
677+
678+
def check_remove_unreferenced_vertices(device, int_t, float_t):
679+
expected_mobius = o3d.t.geometry.TriangleMesh.create_mobius(
680+
10, 2, 1, 1, 1, 1, 1, float_t, int_t, device)
681+
682+
verts = o3c.Tensor(
683+
[[0.5, 0.0, 0.0], [1.5, 0.0, 0.0], [0.424307, 0.308277, -0.154508],
684+
[1.19373, 0.867294, 0.154508], [0.184017, 0.566346, -0.293893],
685+
[0.434017, 1.33577, 0.293893], [-0.218199, 0.671548, -0.404508],
686+
[-0.399835, 1.23057, 0.404508], [-0.684017, 0.496967, -0.475528],
687+
[-0.934017, 0.678603, 0.475528], [-1.0, 0.0, -0.5], [-1.0, 0.0, 0.5],
688+
[-0.934017, -0.678603, -0.475528], [-0.684017, -0.496967, 0.475528],
689+
[-0.399835, -1.23057, -0.404508], [-0.218199, -0.671548, 0.404508],
690+
[0.434017, -1.33577, -0.293893], [0.184017, -0.566346, 0.293893],
691+
[0, 0, 0], [1.19373, -0.867294, -0.154508], [1, 1, 1],
692+
[0.424307, -0.308277, 0.154508]], float_t, device)
693+
694+
tris = o3c.Tensor(
695+
[[0, 3, 1], [0, 2, 3], [3, 2, 4], [3, 4, 5], [4, 7, 5], [4, 6, 7],
696+
[7, 6, 8], [7, 8, 9], [8, 11, 9], [8, 10, 11], [11, 10, 12],
697+
[11, 12, 13], [12, 15, 13], [12, 14, 15], [15, 14, 16], [15, 16, 17],
698+
[16, 21, 17], [16, 19, 21], [19, 21, 1], [1, 21, 0]], int_t, device)
699+
700+
mobius = o3d.t.geometry.TriangleMesh(verts, tris)
701+
mobius.remove_unreferenced_vertices()
702+
assert mobius.vertex.positions.allclose(expected_mobius.vertex.positions)
703+
assert mobius.triangle.indices.allclose(expected_mobius.triangle.indices)
704+
705+
706+
@pytest.mark.parametrize("device", list_devices())
707+
def test_remove_unreferenced_vertices(device):
708+
for int_t in [o3c.int32, o3c.int64]:
709+
for float_t in [o3c.float32, o3c.float64]:
710+
check_no_unreferenced_vertices(device, int_t, float_t)
711+
check_remove_unreferenced_vertices(device, int_t, float_t)

0 commit comments

Comments
 (0)