Mesh processing tools
An overview of algorithms and utilities in the MeshTools namespace.
The MeshTools namespace provides a broad set of tools for transforming, filtering and merging mesh data, operating both on high-level Trade::
Creating MeshData instances from scratch
When it's desirable to create a Trade::
Containers::ArrayView<const UnsignedInt> indices = …; Containers::ArrayView<const Vector3> positions = …; Containers::ArrayView<const Vector3> normals = …; Trade::MeshData mesh = MeshTools::interleave( MeshPrimitive::Triangles, Trade::MeshIndexData{indices}, { Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}, Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}, });
Interleaving isn't the only possible layout option — you can also construct the MeshData instance directly. It's more involved, but gives you an ability to have any packing you need.
Note that, however, a Trade::
Uploading a mesh to the GPU
The MeshTools::
Trade::MeshData data = …; GL::Mesh mesh = MeshTools::compile(data);
For more control over index / vertex data storage and handling custom attributes, the MeshTools::
Data layout optimization
Mesh import in Trade::
Interleaving vertex data
Assuming a mesh is processed vertex-by-vertex with all attributes used, which is the common case for both CPU- and GPU-side operation, the best memory layout is interleaving the data so that attributes for a particular vertex are next to each other in memory.
Trade::MeshData mesh = …; mesh = MeshTools::interleave(std::move(mesh));
Interleaving is however the default behavior in most importers, and most MeshTools algorithms produce interleaved layouts by default as well. MeshTools::
For interleaving of raw data arrays with types known at compile time, there's MeshTools::struct
, another way is for example using Utility::
Packing index data
Especially when importing data from text-based formats such as OBJ, index buffers have full 32-bit values, which is not always necessary. With MeshTools::
if(mesh.isIndexed()) mesh = MeshTools::compressIndices(std::move(mesh));
You can also use the same function to unpack already-packed index data back to a larger type, by passing an appropriate MeshIndexType as the second argument.
Index packing can be also done directly on an index array using MeshTools::
Vertex transform cache optimization
The MeshTools::
PluginManager::Manager<Trade::AbstractSceneConverter> manager; Containers::Pointer<Trade::AbstractSceneConverter> meshOptimizer = manager.loadAndInstantiate("MeshOptimizerSceneConverter"); meshOptimizer->convertInPlace(mesh);
See documentation of Trade::
Index buffer generation
A mesh can be non-indexed, meaning that e.g. each three vertices form a triangle, it can have an index buffer of an arbitrary type, or it can be formed from strips or fans. While such flexibility allows to pick a representation that best fits given topology or use case, it can be a burden for algorithms that need to work with arbitrary input meshes. The MeshTools::
Trade::MeshData indexed = MeshTools::generateIndices(mesh); performSomeProcessing(indexed.indices<UnsignedInt>(), indexed.positions3DAsArray());
Ultimately, if a non-indexed list of primitives is expected, the mesh can be subsequently passed through MeshTools::
Besides the variant taking a Trade::0, 1, 2, 3, 4, 5, ...
sequence if an index buffer needs to be added to a mesh that otherwise doesn't need it. For all of these there are non-allocating generateLineStripIndicesInto() etc. variants as well.
Finally, MeshTools::
Vertex data transformation
The MeshTools::
mesh = MeshTools::transform3D(std::move(mesh), Matrix4::scaling({0.5f, 2.0f, 1.0f}));
If the mesh has the to-be-transformed attributes in a floating-point format, i.e. not packed in any way, the function can operate directly on the data itself without making a copy. For that reason, if the original unmodified instance isn't needed afterwards anymore, it's again useful to pass a r-value in, as shown in the snippet. Alternatively, the transform2DInPlace(), transform3DInPlace() and transformTextureCoordinates2DInPlace() variants operate in-place, not modifying the attribute layout in any way, but have with additional restrictions on the attribute types.
Joining multiple meshes together
While models usually contain multiple smaller meshes because it makes editing easier, for rendering it's often better to batch them together. The MeshTools::
Trade::MeshData sphere = …; Trade::MeshData cube = …; Trade::MeshData cylinder = …; Trade::MeshData primitives = MeshTools::concatenate({sphere, cube, cylinder}); GL::Mesh mesh = MeshTools::compile(primitives);
If not, their relative order is preserved in the output, so you can for example render each individual piece separately by passing an appropriate index range to GL::
GL::MeshView meshSphereView{mesh}, meshCubeView{mesh}, meshCylinderView{mesh}; meshSphereView .setIndexOffset(0) .setCount(sphere.indexCount()); meshCubeView .setIndexOffset(meshSphereView.indexOffset() + meshSphereView.count()) .setCount(cube.indexCount()); meshCylinderView .setIndexOffset(meshCubeView.indexOffset() + meshCubeView.count()) .setCount(cylinder.indexCount()); shader .setColor(0x2f83cc_rgbf) .draw(meshSphereView) .setColor(0x3bd267_rgbf) .draw(meshCubeView) .setColor(0xc7cf2f_rgbf) .draw(meshCylinderView);
Meshes joined this way can make use of various rendering optimizations, see Multidraw and reducing driver overhead for the shader-side details. There's also a MeshTools::
Inserting additional attributes into an existing mesh
The MeshTools::
Containers::ArrayView<const Color3> vertexColors = …; Trade::MeshData coloredCube = MeshTools::interleave(Primitives::cubeSolid(), { Trade::MeshAttributeData{Trade::MeshAttribute::Color, vertexColors} });
It's also possible to add just an uninitialized attribute placeholder, specifying just the desired type, and copy the data to it later using mutable MeshData attribute access:
Trade::MeshData coloredCube = MeshTools::interleave(Primitives::cubeSolid(), { Trade::MeshAttributeData{Trade::MeshAttribute::Color, VertexFormat::Vector3, nullptr} }); for(Color3& i: coloredCube.mutableAttribute<Color3>(Trade::MeshAttribute::Color)) i = …;
Similar functionality is available also in the above-mentioned MeshTools::
Trade::MeshAttribute VertexIdAttribute = Trade::meshAttributeCustom(…); Trade::MeshData vertexIdMesh = MeshTools::duplicate(mesh, { Trade::MeshAttributeData{VertexIdAttribute, VertexFormat::UnsignedInt, nullptr} }); UnsignedInt id = 0; for(UnsignedInt& i: vertexIdMesh.mutableAttribute<UnsignedInt>(VertexIdAttribute)) i = id++;
In case you need to insert attributes that differ not per vertex but per face, MeshTools::
Containers::ArrayView<const Color3> faceColors = …; Trade::MeshData meshWithFaceColors = MeshTools::combineFaceAttributes(mesh, { Trade::MeshAttributeData{Trade::MeshAttribute::Color, faceColors} });
Finally, for scenarios where a mesh has per-attribute index buffers, such as is the case when directly importing data from OBJ files, MeshTools::
Filtering mesh attributes
The inverse of attribute insertion is possible with MeshTools::
Trade::MeshData positionsNormals = MeshTools::filterOnlyAttributes(mesh, { Trade::MeshAttribute::Position, Trade::MeshAttribute::Normal });
This avoids a needless copy in cases the result is passed to other algorithms that perform further operations on the data. If filtering is the final step, pass the result to MeshTools::
positionsNormals = MeshTools::interleave(positionsNormals, {}, {});
Duplicate vertex removal, duplication based on an index buffer
The MeshTools::
Trade::MeshData deduplicated = MeshTools::removeDuplicatesFuzzy(mesh);
The fuzzy thresholds are adjustable and setting them to higher values can perform rudimentary mesh simplification, but for a robust behavior with higher simplification ratios it's recommended to use the MeshOptimizerSceneConverter simplification feature instead. Here for example attempting to reduce the mesh index count by a factor of 10:
PluginManager::Manager<Trade::AbstractSceneConverter> manager; Containers::Pointer<Trade::AbstractSceneConverter> meshOptimizer = manager.loadAndInstantiate("MeshOptimizerSceneConverter"); meshOptimizer->configuration().setValue("simplify", true); meshOptimizer->configuration().setValue("simplifyTargetIndexCountThreshold", 0.1f); Containers::Optional<Trade::MeshData> simplified = meshOptimizer->convert(mesh);
Internally, the duplicate vertex removal is implemented using MeshTools::*Into()
variants. These functions return an index array that maps from the original data to the deduplicated locations. Commonly, the index array eventually becomes an index buffer of the resulting mesh, or one uses the MeshTools::
Another use case for the index buffer returned by these functions is to perform an operation of on the deduplicated data and then apply the updates back to the original. One such scenario is for example with soft body simulation, where a simulation engine requires a watertight indexed mesh containing only positions. Rendering however usually needs at least normals as well, and potentially texture coordinates, vertex colors and others. Assuming an indexed mesh with arbitrary attributes, a watertight mesh consisting of just indexed positions would be made like this:
Containers::Array<Vector3> positions = mesh.positions3DAsArray(); /* Deduplicate the positions and create a mapping array */ Containers::Pair<Containers::Array<UnsignedInt>, std::size_t> out = MeshTools::removeDuplicatesFuzzyInPlace( Containers::arrayCast<2, Float>(stridedArrayView(positions))); Containers::Array<UnsignedInt> indexMapping = std::move(out.first()); arrayResize(positions, out.second()); /* Combine the original index buffer with the mapping array */ Containers::Array<UnsignedInt> positionIndices = MeshTools::duplicate( Containers::StridedArrayView1D<const UnsignedInt>{indexMapping}, Containers::StridedArrayView1D<const UnsignedInt>{mesh.indicesAsArray()});
The positionIndices
are made in two steps instead of using MeshTools::indexMapping
, without being mixed with the original index buffer, needs to be preserved for a later use. Once a simulation updates the positions, they get copied back to the original mesh:
performSimulation(positionIndices, positions); /* Copy updated positions back to the original locations in the mesh */ MeshTools::duplicateInto( Containers::StridedArrayView1D<const UnsignedInt>{indexMapping}, Containers::StridedArrayView1D<const Vector3>{positions}, mesh.mutableAttribute<Vector3>(Trade::MeshAttribute::Position));
Bounding volume calculation
The MeshTools::
Memory ownership helpers
Much like all other heavier data structures in Magnum, a Trade::
Another use case for it is creating a self-contained Trade::
Trade::MeshData skybox = MeshTools::copy(Primitives::cubeSolid()); MeshTools::flipNormalsInPlace(skybox.mutableIndices<UnsignedInt>(), skybox.mutableAttribute<Vector3>(Trade::MeshAttribute::Normal));
An inverse to MeshTools::const Trade::MeshData&
and Trade::MeshData&
.