Skip to content

Commit 664ef13

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 664ef13

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

cpp/open3d/t/geometry/TriangleMesh.cpp

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

1255+
TriangleMesh TriangleMesh::RemoveUnreferencedVertices() {
1256+
if (!HasVertexPositions() || GetVertexPositions().GetLength() == 0) {
1257+
utility::LogWarning(
1258+
"[RemoveUnreferencedVertices] TriangleMesh has no vertices.");
1259+
return *this;
1260+
}
1261+
GetVertexAttr().AssertSizeSynchronized();
1262+
1263+
core::Dtype tri_dtype = HasTriangleIndices()
1264+
? GetTriangleIndices().GetDtype()
1265+
: core::Int64;
1266+
1267+
int64_t num_verts_old = GetVertexPositions().GetLength();
1268+
// int mask for vertices as we need to remap indices.
1269+
core::Tensor vertex_mask = core::Tensor::Zeros({num_verts_old}, tri_dtype);
1270+
1271+
if (!HasTriangleIndices() || GetTriangleIndices().GetLength() == 0) {
1272+
utility::LogWarning(
1273+
"[RemoveUnreferencedVertices] TriangleMesh has no triangles. "
1274+
"Removing all vertices.");
1275+
// in this case we need to empty vertices and their attributes
1276+
} else {
1277+
GetTriangleAttr().AssertSizeSynchronized();
1278+
core::Tensor tris_cpu =
1279+
GetTriangleIndices().To(core::Device()).Contiguous();
1280+
DISPATCH_INT_DTYPE_PREFIX_TO_TEMPLATE(tri_dtype, tris, [&]() {
1281+
scalar_tris_t *tris_ptr = tris_cpu.GetDataPtr<scalar_tris_t>();
1282+
scalar_tris_t *vertex_mask_ptr =
1283+
vertex_mask.GetDataPtr<scalar_tris_t>();
1284+
for (int i = 0; i < tris_cpu.GetLength(); i++) {
1285+
vertex_mask_ptr[tris_ptr[3 * i]] = 1;
1286+
vertex_mask_ptr[tris_ptr[3 * i + 1]] = 1;
1287+
vertex_mask_ptr[tris_ptr[3 * i + 2]] = 1;
1288+
}
1289+
1290+
UpdateTriangleIndicesByVertexMask<scalar_tris_t>(tris_cpu,
1291+
vertex_mask);
1292+
});
1293+
}
1294+
1295+
// send the vertex mask to original device and apply to
1296+
// vertices
1297+
vertex_mask = vertex_mask.To(GetDevice(), core::Bool);
1298+
for (auto item : GetVertexAttr()) {
1299+
SetVertexAttr(item.first, item.second.IndexGet({vertex_mask}));
1300+
}
1301+
1302+
utility::LogDebug(
1303+
"[RemoveUnreferencedVertices] {:d} vertices have been removed.",
1304+
(int)(num_verts_old - GetVertexPositions().GetLength()));
1305+
1306+
return *this;
1307+
}
1308+
12551309
} // namespace geometry
12561310
} // namespace t
12571311
} // 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: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,5 +1212,135 @@ TEST_P(TriangleMeshPermuteDevices, SelectByIndex) {
12121212
box_untouched.GetTriangleIndices()));
12131213
}
12141214

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