Created
June 1, 2023 21:55
-
-
Save langthom/7b3be42ba31d3c8356d092f2ddc88e2f to your computer and use it in GitHub Desktop.
Utility functions for in-memory conversion between OpenMesh meshes and vtkPolyData.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2023 Dr. Thomas Lang | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |
# and associated documentation files (the “Software”), to deal in the Software without restriction, | |
# including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |
# subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT | |
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
cmake_minimum_required(VERSION 3.2) | |
project(MeshConverter) | |
# The little test project. | |
add_executable(MeshConverter | |
MeshConverter.h | |
Main.cpp | |
) | |
# Requires at least VTK 8.1, tested on VTK 8.1. | |
# More modern versions should also be supported. | |
find_package(VTK REQUIRED) | |
target_include_directories(MeshConverter SYSTEM PUBLIC ${VTK_INCLUDE_DIRS}) | |
target_link_libraries(MeshConverter PUBLIC ${VTK_LIBRARIES}) | |
# Requires OpenMesh. | |
find_package(OpenMesh REQUIRED) | |
target_compile_definitions(MeshConverter PRIVATE -D_USE_MATH_DEFINES -DOM_STATIC_BUILD) # Necessary (on Windows) for functioning OpenMesh and I/O | |
target_include_directories(MeshConverter SYSTEM PUBLIC ${OPENMESH_INCLUDE_DIRS}) | |
target_link_libraries(MeshConverter PUBLIC OpenMeshCore OpenMeshTools) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2023 Dr. Thomas Lang | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |
* and associated documentation files (the “Software”), to deal in the Software without restriction, | |
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |
* subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT | |
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#include "MeshConverter.h" | |
#include <iostream> | |
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> | |
#include <OpenMesh/Core/IO/MeshIO.hh> | |
#include <vtkPLYWriter.h> | |
#include <vtkPLYReader.h> | |
int main(int argc, char** argv) { | |
if (argc < 2) { | |
std::cerr << "Usage: " << argv[0] << " <path/to/input/mesh>\n"; | |
return EXIT_FAILURE; | |
} | |
std::string pathToInputMesh = argv[1]; | |
// Our mesh is a simple triangular mesh using the popular ArrayKernel. | |
// Please note that we only support triangular meshes, although the | |
// implementation could be generalized. | |
using OpenMeshMesh = OpenMesh::TriMesh_ArrayKernelT<>; | |
// Test case (1) | |
// 1. Read triangular mesh from the specified input file. | |
// 2. Convert the OpenMesh mesh to the vtkPolyData representation. | |
// 3. Write the converted mesh to disk using vtkPLYWriter. | |
OpenMeshMesh inputOMMesh; | |
if (!OpenMesh::IO::read_mesh(inputOMMesh, pathToInputMesh)) { | |
std::cerr << "Reading of TriMesh from '" << pathToInputMesh << "' failed.\n"; | |
} | |
auto outputVTKMesh = vtkSmartPointer< vtkPolyData >::New(); | |
om_vtk_conversion::convert_om_to_vtk(outputVTKMesh, inputOMMesh); | |
auto vtkOutputFname = pathToInputMesh.substr(0, pathToInputMesh.size() - 4) + "_vtk.ply"; | |
auto vtkMeshWriter = vtkSmartPointer< vtkPLYWriter >::New(); | |
vtkMeshWriter->SetFileName(vtkOutputFname.c_str()); | |
vtkMeshWriter->SetInputData(outputVTKMesh); | |
vtkMeshWriter->SetFileTypeToBinary(); | |
vtkMeshWriter->Write(); | |
// Test case (2) | |
// - Inverse to case (1), read PLY via VTK, convert it to OpenMesh and write via OpenMesh. | |
auto vtkMeshReader = vtkSmartPointer< vtkPLYReader >::New(); | |
vtkMeshReader->SetFileName(pathToInputMesh.c_str()); | |
vtkMeshReader->Update(); | |
auto inputVTKMesh = vtkMeshReader->GetOutput(); | |
OpenMeshMesh outputOMMesh; | |
om_vtk_conversion::convert_vtk_to_om(outputOMMesh, inputVTKMesh); | |
auto omOutputFname = pathToInputMesh.substr(0, pathToInputMesh.size() - 4) + "_om.ply"; | |
if (!OpenMesh::IO::write_mesh(outputOMMesh, omOutputFname, OpenMesh::IO::Options::Binary)) { | |
std::cerr << "Writing of TriMesh to '" << omOutputFname << "' failed.\n"; | |
} | |
return EXIT_SUCCESS; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2023 Dr. Thomas Lang | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |
* and associated documentation files (the “Software”), to deal in the Software without restriction, | |
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, | |
* subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT | |
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#ifndef MESH_CONVERTER_H | |
#define MESH_CONVERTER_H | |
#include <vtkPolyData.h> | |
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> | |
namespace om_vtk_conversion { | |
/// <summary> | |
/// Convert an instance of <c>vtkPolyData</c> known to house a triangular mesh into an instance | |
/// of <c>OpenMesh</c>, specifically to a <c>OpenMesh::TriMesh_ArrayKernelT</c> instantiated with | |
/// the specified <typeparamref name="OMMeshTraits">OMMeshTraits</typeparamref>. Currently, we only support such triangular meshes. | |
/// Upon invocation, the passed <paramref name="mesh">mesh</paramref> will be cleared through the passed reference. | |
/// </summary> | |
/// <typeparam name="OMMeshTraits"> | |
/// Traits with which the <c>OpenMesh::TriMesh_ArrayKernelT</c> template will be instantiated. | |
/// Defaults to <c>OpenMesh::DefaultTraits</c>. | |
/// </typeparam> | |
/// <param name="omMesh"> | |
/// The mesh that will be first cleared entirely and then populated with vertices and faces. | |
/// </param> | |
/// <param name="vtkMesh"> | |
/// The input mesh whose vertices and connectivity will be read. | |
/// Must contain the necessary three-dimensional points and must have a valid <i>polys</i> array. | |
/// </param> | |
template< class OMMeshTraits = OpenMesh::DefaultTraits > | |
void convert_vtk_to_om(OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >& omMesh, vtkSmartPointer< vtkPolyData > vtkMesh) { | |
using OMMesh = OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >; | |
static_assert(OMMesh::IsTriMesh, "This function only accepts TriMesh instances."); | |
static constexpr int verticesPerFace = 3; | |
// Clear the mesh in any case. | |
omMesh.clear(); | |
// Step 1: Fill in the points. There, we simply read the points from the | |
// VTK mesh and insert them into the OpenMesh mesh _in the same order_. | |
auto points = vtkMesh->GetPoints(); | |
auto nr_points = points->GetNumberOfPoints(); | |
for (vtkIdType pointIndex = 0; pointIndex < nr_points; ++pointIndex) { | |
double coordinates[3]; | |
points->GetPoint(pointIndex, coordinates); | |
omMesh.add_vertex(typename OMMesh::Point(coordinates)); | |
} | |
// Step 2: Add the faces. | |
// As we already added the vertices to the mesh, we can add the faces | |
// by iterating over them and asking vtkPolyData, which vertices belong | |
// to each face. These are only indices into the array of vertices | |
// within vtkPolyData. Now, since we added the vertices in OpenMesh | |
// in the very same order, we can simply use the vertex ids in that | |
// order, ask the OpenMesh mesh which handle it actually is, and | |
// construct and finally add the face. | |
// Internal note: | |
// As we only support triangular meshes, we could fully initialize the following | |
// vector and work with direct elemental access. However, to make this code, in | |
// principle, generalizable, we choose the "push_back + clear" solution. | |
std::vector< typename OMMesh::VertexHandle > vertexHandlesPerFace; | |
vertexHandlesPerFace.reserve(verticesPerFace); | |
auto polys = vtkMesh->GetPolys(); | |
auto vertexIds = vtkSmartPointer< vtkIdList >::New(); | |
for (polys->InitTraversal(); polys->GetNextCell(vertexIds);) { | |
// Iterate over each "cell", where a cell is a polygon here. | |
vtkIdType nr_vertices = vertexIds->GetNumberOfIds(); | |
for (vtkIdType vi = 0; vi < nr_vertices; ++vi) { | |
vtkIdType vtkVertexID = vertexIds->GetId(vi); // The vertex id within vtkPolyData. | |
vertexHandlesPerFace.push_back(omMesh.vertex_handle(vtkVertexID)); // Mapping to OpenMesh handles. | |
} | |
omMesh.add_face(vertexHandlesPerFace); | |
vertexHandlesPerFace.clear(); | |
} | |
} | |
/// <summary> | |
/// Convert an instance of a triangular <c>OpenMesh</c> mesh, specifically a <c>OpenMesh::TriMesh_ArrayKernelT</c> instantiated with | |
/// the specified <typeparamref name="OMMeshTraits">OMMeshTraits</typeparamref>, into an instance of <c>vtkPolyData</c>. | |
/// Currently, we only support such triangular meshes. | |
/// </summary> | |
/// <typeparam name="OMMeshTraits"> | |
/// Traits with which the <c>OpenMesh::TriMesh_ArrayKernelT</c> template will be instantiated. | |
/// Defaults to <c>OpenMesh::DefaultTraits</c>. | |
/// </typeparam> | |
/// <param name="vtkMesh"> | |
/// The output mesh to be filled with the point and polygon information. | |
/// </param> | |
/// <param name="omMesh"> | |
/// The input mesh, must be a valid <c>OpenMesh</c> mesh. | |
/// </param> | |
template< class OMMeshTraits = OpenMesh::DefaultTraits > | |
void convert_om_to_vtk(vtkSmartPointer< vtkPolyData > vtkMesh, OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >& omMesh) { | |
using OMMesh = OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >; | |
static_assert(OMMesh::IsTriMesh, "This function only accepts TriMesh instances."); | |
static constexpr int verticesPerFace = 3; | |
auto nr_vertices = omMesh.n_vertices(); | |
auto nr_faces = omMesh.n_faces(); | |
// Step 1: Add the vertices / points to the VTK mesh. | |
auto vertices = vtkSmartPointer< vtkPoints >::New(); | |
vertices->Allocate(nr_vertices); | |
for (int vertexID = 0; vertexID < nr_vertices; ++vertexID) { | |
auto const& point = omMesh.point(typename OMMesh::VertexHandle(vertexID)); | |
double coordinates[3] = { | |
static_cast< double >(point[0]), | |
static_cast< double >(point[1]), | |
static_cast< double >(point[2]) | |
}; | |
vertices->InsertPoint(vertexID, coordinates); | |
} | |
// Step 2: Add the faces, i.e., for each cell in the input mesh, create an appropriate VTK cell. | |
auto faces = vtkSmartPointer< vtkCellArray >::New(); | |
faces->Allocate(faces->EstimateSize(nr_vertices, verticesPerFace)); | |
// - The connectivity array holding the order of vertex IDs that form the triangle/polygon. | |
auto connectivity = vtkSmartPointer< vtkIdList >::New(); | |
connectivity->SetNumberOfIds(verticesPerFace); | |
// - Process each face. | |
for (auto& faceHandleIter : omMesh.faces()) { | |
// For each vertex associated to the current face, add it to the connectivity in the order linked in the mesh. | |
vtkIdType vertexID = 0; | |
faceHandleIter.vertices().for_each([&](auto& vertexHandle) { connectivity->SetId(vertexID++, vertexHandle.idx()); }); | |
faces->InsertNextCell(connectivity); | |
} | |
vtkMesh->SetPoints(vertices); | |
vtkMesh->SetPolys(faces); | |
} | |
} // namespace om_vtk_conversion | |
#endif // MESH_CONVERTER_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment