Skip to content

Commit a82004c

Browse files
cdtwiggmeta-codesync[bot]
authored andcommitted
Add splitMeshByTextureRegion with tests (#1060)
Summary: Pull Request resolved: #1060 Add splitMeshByTextureRegion which splits boundary triangles along texture region boundaries using binary search in UV space, producing clean submeshes. Also add Python bindings for the Mesh-level reduceMeshByFaces and reduceMeshByVertices functions that work without a Character. The splitting algorithm classifies each texcoord vertex as inside/outside by sampling the texture, then for boundary faces finds crossing points via binary search (8 iterations = 1/256 sub-pixel accuracy). Edge caching ensures vertex reuse and watertight geometry at UV seams. Includes tests verifying mesh validity (manifold topology, consistent winding, no degenerate triangles), area conservation, and boundary vertex placement. Regenerated stubs. Reviewed By: cstollmeta Differential Revision: D93700499 fbshipit-source-id: cfa159d76923a0f5e859c776221152b9061a7585
1 parent 4138f7c commit a82004c

File tree

4 files changed

+720
-12
lines changed

4 files changed

+720
-12
lines changed

pymomentum/geometry/geometry_pybind.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,42 @@ blend shapes, pose shapes, and other mesh-related data.
11771177
py::arg("character"),
11781178
py::arg("active_faces"));
11791179

1180+
// reduceMeshByVertices(mesh, activeVertices) — Mesh-level overload
1181+
m.def(
1182+
"reduce_mesh_by_vertices",
1183+
[](const momentum::Mesh& mesh, const py::array_t<bool>& activeVertices) {
1184+
return momentum::reduceMeshByVertices(mesh, boolArrayToVector(activeVertices));
1185+
},
1186+
R"(Reduces a standalone mesh to only include the specified vertices and associated faces.
1187+
1188+
This is the :class:`Mesh`-only version that works without a :class:`Character`. It handles
1189+
vertices, normals, colors, confidence, faces, lines, texcoords, texcoord_faces, and
1190+
texcoord_lines with proper index remapping and compaction.
1191+
1192+
:param mesh: The mesh to reduce.
1193+
:param active_vertices: A boolean array marking which vertices should be retained.
1194+
:return: A new :class:`Mesh` containing only the marked vertices and their associated faces.)",
1195+
py::arg("mesh"),
1196+
py::arg("active_vertices"));
1197+
1198+
// reduceMeshByFaces(mesh, activeFaces) — Mesh-level overload
1199+
m.def(
1200+
"reduce_mesh_by_faces",
1201+
[](const momentum::Mesh& mesh, const py::array_t<bool>& activeFaces) {
1202+
return momentum::reduceMeshByFaces(mesh, boolArrayToVector(activeFaces));
1203+
},
1204+
R"(Reduces a standalone mesh to only include the specified faces and associated vertices.
1205+
1206+
This is the :class:`Mesh`-only version that works without a :class:`Character`. It handles
1207+
vertices, normals, colors, confidence, faces, lines, texcoords, texcoord_faces, and
1208+
texcoord_lines with proper index remapping and compaction.
1209+
1210+
:param mesh: The mesh to reduce.
1211+
:param active_faces: A boolean array marking which faces should be retained.
1212+
:return: A new :class:`Mesh` containing only the marked faces and their referenced vertices.)",
1213+
py::arg("mesh"),
1214+
py::arg("active_faces"));
1215+
11801216
// reduceToSelectedModelParameters(character, activeParameters)
11811217
m.def(
11821218
"reduce_to_selected_model_parameters",
@@ -1369,6 +1405,41 @@ The sampling strategy is deterministic and uses barycentric coordinates.
13691405
py::arg("threshold") = 0.0f,
13701406
py::arg("num_samples") = 3);
13711407

1408+
// split_mesh_by_texture_region(mesh, texture, region_colors, num_binary_search_steps)
1409+
m.def(
1410+
"split_mesh_by_texture_region",
1411+
&splitMeshByTextureRegion,
1412+
R"(Split mesh triangles along texture region boundaries via binary search.
1413+
1414+
For each face in the mesh, this function classifies its texcoord vertices by sampling
1415+
the texture at their UV positions. Faces fully inside the region are kept unchanged.
1416+
Faces fully outside are discarded. Boundary faces (with mixed inside/outside vertices)
1417+
are split by finding the crossing point along each edge via binary search in UV space,
1418+
creating new vertices and sub-triangles. The result is compacted to remove unused
1419+
vertices and texcoords.
1420+
1421+
This is useful for extracting a clean sub-mesh corresponding to a painted texture region,
1422+
with the boundary triangles cut precisely along the region boundary rather than being
1423+
included or excluded whole.
1424+
1425+
:param mesh: The :class:`Mesh` containing vertices, faces, texcoords, and texcoord_faces.
1426+
Must have non-empty texcoords and texcoord_faces.
1427+
:param texture: RGB texture image as a numpy array with shape ``[height, width, 3]``
1428+
and dtype ``uint8``.
1429+
:param region_colors: RGB colors defining the "inside" region as a numpy array with shape
1430+
``[n_colors, 3]`` and dtype ``uint8``. A texcoord vertex is considered
1431+
"inside" if the texture color at its UV matches any of these colors.
1432+
:param num_binary_search_steps: Number of binary search iterations for finding edge crossing
1433+
points. Higher values give more accurate boundaries. 8 iterations
1434+
gives 1/256 sub-pixel accuracy. Defaults to 8.
1435+
:return: A new :class:`Mesh` containing only the inside portion of the original mesh, with
1436+
boundary triangles split along the texture region boundary.
1437+
)",
1438+
py::arg("mesh"),
1439+
py::arg("texture"),
1440+
py::arg("region_colors"),
1441+
py::arg("num_binary_search_steps") = 8);
1442+
13721443
// Register GltfBuilder bindings
13731444
registerGltfBuilderBindings(m);
13741445
}

0 commit comments

Comments
 (0)