mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
21 KiB
411 lines
21 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022, 2023, 2024, 2025, 2026 |
|
Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
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. |
|
*/ |
|
|
|
namespace Magnum { |
|
/** @page meshtools Mesh processing tools |
|
@brief Overview of algorithms and utilities in the @ref MeshTools namespace |
|
|
|
@m_keywords{MeshTools} |
|
|
|
@tableofcontents |
|
@m_footernavigation |
|
|
|
The @ref MeshTools namespace provides a broad set of tools for transforming, |
|
filtering, optimizing and merging mesh data, operating both on high-level |
|
@ref Trade::MeshData instances as well as directly on concrete data views. |
|
|
|
@todoc subdivide isn't mentioned because it's useless, neither is |
|
fullScreenTriangle |
|
@todoc normal generation and flipping once it's more than "sorry this is shit" |
|
|
|
@section meshtools-create Creating MeshData instances from scratch |
|
|
|
When it's desirable to create a @ref Trade::MeshData instance from scratch, for |
|
example from runtime-generated data, @ref MeshTools::interleave(MeshPrimitive, const Trade::MeshIndexData&, Containers::ArrayView<const Trade::MeshAttributeData>) "MeshTools::interleave()" |
|
is the most convenient way. |
|
|
|
@snippet MeshTools.cpp interleave-meshdata |
|
|
|
Interleaving isn't the only possible layout option --- you can also |
|
@ref Trade-MeshData-populating "construct the MeshData instance directly". It's |
|
more involved, but gives you an ability to have any packing you need. |
|
|
|
Note that, however, a @ref Trade::MeshData instance isn't *required* in many |
|
cases --- most @ref MeshTools algorithms, including for example normal |
|
generation or duplicate removal, have alternatives that operate directly on |
|
plain data arrays, and wrapping those in a @ref Trade::MeshData instance just |
|
to call a function may be a needless complication. On the other hand, having a |
|
@ref Trade::MeshData instance may be beneficial when it's needed to abstract |
|
away optional vertex attributes or when the types or layout can be arbitrary. |
|
The only interface that unconditionally relies on @ref Trade::MeshData is the |
|
@ref Trade::AbstractSceneConverter, for example when you want to export a mesh |
|
to a file or when you want to call an external library to perform advanced |
|
tasks on the data. |
|
|
|
@section meshtools-gpu Uploading a mesh to the GPU |
|
|
|
The @ref MeshTools::compile() utility creates a @ref GL::Mesh instance out of |
|
an arbitrary @ref Trade::MeshData, binding builtin attributes listed in the |
|
@ref Trade::MeshData enum to known locations defined in @ref Shaders::GenericGL. |
|
|
|
@snippet Trade.cpp MeshData-gpu-opengl |
|
|
|
For more control over index / vertex data storage and handling custom |
|
attributes, the @ref MeshTools::compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) |
|
overload can be used. Additionally, there's @ref MeshTools::compileLines() with |
|
specialized handling for line meshes to be rendered with @ref Shaders::LineGL, |
|
and the @ref MeshTools::compiledPerVertexJointCount() utility gives back a |
|
count of primary and secondary per-vertex joints for setting up an appropriate |
|
@ref shaders-usage-skinning "skinning shader". |
|
|
|
@section meshtools-optimization Data layout optimization |
|
|
|
Mesh import in @ref Trade::AbstractImporter subclasses is commonly done so that |
|
the input layout is preserved as much as possible, and without performing |
|
non-essential operations. Similarly, meshes coming from the @ref Primitives |
|
library preference common, unsurprising formats over the most efficient |
|
representation. Thus, depending on quality of the input data, the target use |
|
case and whether given mesh is used for further processing or rendering, there |
|
are various optimization possibilities. |
|
|
|
@subsection meshtools-optimization-interleave 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. |
|
|
|
@snippet MeshTools.cpp meshtools-interleave |
|
|
|
Interleaving is however the default behavior in most importers, and most |
|
@ref MeshTools algorithms produce interleaved layouts by default as well. |
|
@ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "MeshTools::interleave()" |
|
is thus implicitly a passthrough in case the data is already interleaved, so |
|
it's often desirable to pass a r-value there like shown above, which causes it |
|
to be just moved through if nothing needs to be done. |
|
|
|
For interleaving of raw data arrays with types known at compile time, there's |
|
@ref MeshTools::interleave(const T&, const U&... next), or its non-allocating |
|
@ref MeshTools::interleaveInto() variant. If the vertex layout is a concrete |
|
@cpp struct @ce, another way is for example using |
|
@relativeref{Corrade,Utility::copy()} to members slices made with |
|
@relativeref{Corrade,Containers::StridedArrayView::slice(U T::*) const}. |
|
|
|
@subsection meshtools-optimization-pack-indices 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 |
|
@ref MeshTools::compressIndices(const Trade::MeshData&, MeshIndexType) "MeshTools::compressIndices()" the index buffer gets reduced to a smaller type. |
|
Because vertex data are untouched by this operation, it's again desirable to |
|
pass a r-value in, which causes vertex data to be moved through instead of |
|
copying: |
|
|
|
@snippet MeshTools.cpp meshtools-compressindices |
|
|
|
You can also use the same function to unpack already-packed index data back to |
|
a larger type, by passing an appropriate @ref MeshIndexType as the second |
|
argument. |
|
|
|
Index packing can be also done directly on an index array using |
|
@ref MeshTools::compressIndices(const Containers::StridedArrayView1D<const UnsignedInt>&, MeshIndexType, Long). |
|
There's no non-allocating variant in this case, because one would have to do |
|
another pass over the index array to figure out the target array size first. If |
|
you want to perform packing to a concrete type, use one of the |
|
@ref Math::castInto() overloads. |
|
|
|
@subsection meshtools-optimization-cache Vertex transform cache optimization |
|
|
|
The @ref MeshTools::tipsify() utility reorders the index buffer in a way that |
|
tries to maximize use of GPU vertex cache, resulting in possibly faster |
|
rendering. It's however recommended to use the |
|
@relativeref{Trade,MeshOptimizerSceneConverter} plugin instead if possible. It |
|
contains a set of state-of-the-art algorithms and by default performs a |
|
non-destructive sequence of optimizations that make the mesh faster to render |
|
without affecting appearance in any way. |
|
|
|
@snippet MeshTools.cpp meshtools-meshoptimizer |
|
|
|
See documentation of @ref Trade::AbstractSceneConverter for more information |
|
about using the plugin interface. |
|
|
|
@m_class{m-note m-success} |
|
|
|
@par |
|
If you're dealing with meshes loaded from files, you can call |
|
the @ref magnum-sceneconverter "magnum-sceneconverter" utility with |
|
`-C MeshOptimizerSceneConverter` to perform these optimizations offline. |
|
|
|
@section meshtools-index 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 @ref MeshTools::generateIndices() |
|
helper takes an arbitrary mesh and produces an instance that always has a |
|
32-bit index buffer and has one of the base primitive types such as |
|
@ref MeshPrimitive::Triangles, @relativeref{MeshPrimitive,Lines} or |
|
@relativeref{MeshPrimitive,Points}, so it can then be then passed straight to |
|
an algorithm that expects indexed primitives. |
|
|
|
@snippet MeshTools.cpp meshtools-generateindices |
|
|
|
Ultimately, if a non-indexed list of primitives is expected, the mesh can be |
|
subsequently passed through @ref MeshTools::duplicate(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>) "MeshTools::duplicate()". |
|
|
|
Besides the variant taking a @ref Trade::MeshData, there are low-level |
|
@ref MeshTools::generateLineStripIndices(), |
|
@relativeref{MeshTools,generateLineLoopIndices()}, |
|
@relativeref{MeshTools,generateTriangleStripIndices()} and |
|
@relativeref{MeshTools,generateTriangleFanIndices()} utilities producing an |
|
index buffer corresponding to a primitive strip, loop or fan. To complete the |
|
offering, @relativeref{MeshTools,generateTrivialIndices()} outputs a |
|
@cpp 0, 1, 2, 3, 4, 5, ... @ce 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 @relativeref{MeshTools,generateLineStripIndicesInto()} etc. |
|
variants as well. |
|
|
|
Finally, @ref MeshTools::generateQuadIndices() / |
|
@relativeref{MeshTools,generateQuadIndicesInto()} creates a triangle index |
|
buffer for a list of (indexed) quads. It additionally takes vertex positions to |
|
favor edges that don't create overlapping or too thin triangles. |
|
|
|
@htmlinclude triangulate.svg |
|
|
|
@section meshtools-vertex Vertex data transformation |
|
|
|
The @ref MeshTools::transform2D(), @relativeref{MeshTools,transform3D()} and |
|
@relativeref{MeshTools,transformTextureCoordinates2D()} functions can be used |
|
to bake a transformation into vertex positions, tangent space and texture |
|
coordinates. One use case is preparing a set of meshes to be joined together, |
|
baking their transform hierarchy directly into the data, or for example making |
|
a mesh ready to be used with a renderer that doesn't support passing scaling or |
|
texture coordinate transformation as a parameter. |
|
|
|
@snippet MeshTools.cpp meshtools-transform |
|
|
|
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 |
|
@relativeref{MeshTools,transform2DInPlace()}, |
|
@relativeref{MeshTools,transform3DInPlace()} and |
|
@relativeref{MeshTools,transformTextureCoordinates2DInPlace()} variants operate |
|
in-place, not modifying the attribute layout in any way, but have with |
|
additional restrictions on the attribute types. |
|
|
|
@todoc mention the data overloads, once they're not laughable inline wrappers |
|
over singular Math APIs |
|
|
|
@section meshtools-concatenate 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 |
|
@ref MeshTools::concatenate() function concatenates several input meshes into |
|
a single one. Then, if all the input meshes were using the same material and |
|
were already in their final transform relative to each other, you can render or |
|
further process them as a whole: |
|
|
|
@snippet MeshTools-gl.cpp meshtools-concatenate |
|
|
|
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 @ref GL::Mesh::setIndexOffset() and @relativeref{GL::Mesh,setCount()}, |
|
or by making @ref GL::MeshView instances and rendering those instead: |
|
|
|
@snippet MeshTools-gl.cpp meshtools-concatenate-offsets |
|
|
|
Meshes joined this way can make use of various rendering optimizations, see |
|
@ref shaders-usage-multidraw for the shader-side details. There's also a |
|
@ref MeshTools::concatenateInto() variant that reuses a @ref Trade::MeshData |
|
instance with previously allocated buffers to support use cases where meshes |
|
are repeatedly batched on-the-fly. |
|
|
|
@m_class{m-note m-success} |
|
|
|
@par |
|
Joining all meshes in a file with scene hierarchy baked in can be also done |
|
using the `--concatenate-meshes` option of the |
|
@ref magnum-sceneconverter "magnum-sceneconverter" utility. |
|
|
|
@section meshtools-attributes-insert Inserting additional attributes into an existing mesh |
|
|
|
The @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "MeshTools::interleave()" |
|
API shown above can be also used to insert additional attributes to an existing |
|
mesh. The following snippet takes a cube primitive and copies an external |
|
vertex color attribute alongside existing attributes: |
|
|
|
@snippet MeshTools.cpp meshtools-interleave-insert |
|
|
|
It's also possible to add just an uninitialized attribute placeholder, |
|
specifying just the desired type, and copy the data to it later using |
|
@ref Trade-MeshData-access-mutable "mutable MeshData attribute access": |
|
|
|
@m_class{m-console-wrap} |
|
|
|
@snippet MeshTools.cpp meshtools-interleave-insert-placeholder |
|
|
|
Similar functionality is available also in the above-mentioned |
|
@ref MeshTools::duplicate(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>) "MeshTools::duplicate()" |
|
API, with the difference that the attribute gets inserted only after a |
|
duplication based on an index buffer is performed. This is useful for example |
|
when it's desirable to add a value that's different for each vertex: |
|
|
|
@snippet MeshTools.cpp meshtools-duplicate-insert |
|
|
|
In case you need to insert attributes that differ not per vertex but per face, |
|
@ref MeshTools::combineFaceAttributes(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>) "MeshTools::combineFaceAttributes()" |
|
can be used: |
|
|
|
@snippet MeshTools.cpp combineFaceAttributes |
|
|
|
Finally, for scenarios where a mesh has per-attribute index buffers, such as is |
|
the case when directly importing data from OBJ files, |
|
@ref MeshTools::combineIndexedAttributes() can be used to combine them into a |
|
mesh with a single index buffer. |
|
|
|
@section meshtools-attributes-filter Filtering mesh attributes |
|
|
|
The inverse of attribute insertion is possible with |
|
@ref MeshTools::filterAttributes(), @relativeref{MeshTools,filterOnlyAttributes()} |
|
and @relativeref{MeshTools,filterExceptAttributes()}. In this case however, the |
|
operation affects just the metadata and the result is a *non-owning reference* |
|
to data in the original mesh with just the attributes that passed the filter. |
|
In other words, the vertex data stay unchanged, there's just nothing |
|
referencing the data for attributes that were filtered away. |
|
|
|
@snippet MeshTools.cpp meshtools-filter |
|
|
|
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 @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "MeshTools::interleave()" |
|
without @ref MeshTools::InterleaveFlag::PreserveInterleavedAttributes set to |
|
create a copy that contains only the remaining attributes: |
|
|
|
@snippet MeshTools.cpp meshtools-filter-unsparse |
|
|
|
@section meshtools-duplicates Duplicate vertex removal, duplication based on an index buffer |
|
|
|
The @ref MeshTools::removeDuplicates(const Trade::MeshData&) "MeshTools::removeDuplicates()" |
|
function returns a @ref Trade::MeshData instance that has contains only unique |
|
vertices, and has an index buffer that maps them back to their original |
|
locations. Besides cleaning up messy models the function can be also used for |
|
converting non-indexed meshes (imported from STL files, for example) to |
|
indexed. Sometimes bit-exact comparison isn't enough however, and the |
|
@ref MeshTools::removeDuplicatesFuzzy(const Trade::MeshData&, Float, Double) "MeshTools::removeDuplicatesFuzzy()" |
|
variant instead applies a fuzzy comparison to all floating-point attributes. |
|
|
|
@snippet MeshTools.cpp meshtools-removeduplicates |
|
|
|
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 |
|
@relativeref{Trade,MeshOptimizerSceneConverter} simplification feature instead. |
|
Here for example attempting to reduce the mesh index count by a factor of 10: |
|
|
|
@snippet MeshTools.cpp meshtools-meshoptimizer-simplify |
|
|
|
<b></b> |
|
|
|
@m_class{m-note m-success} |
|
|
|
@par |
|
The @ref magnum-sceneconverter "magnum-sceneconverter" utility can perform |
|
duplicate vertex removal in all meshes in a file using |
|
`--remove-duplicate-vertices` or `--remove-duplicate-vertices-fuzzy`. A |
|
MeshOptimizer simplification equivalent to the above snippet is doable with |
|
`-C MeshOptimizerSceneConverter -c simplify,simplifyTargetIndexCountThreshold=0.1`. |
|
|
|
Internally, the duplicate vertex removal is implemented using |
|
@ref MeshTools::removeDuplicatesInPlace(const Containers::StridedArrayView2D<char>&), |
|
@ref MeshTools::removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D<Float>&, Float) |
|
and their non-in-place, and non-allocating `*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 @ref MeshTools::removeDuplicatesIndexedInPlace() |
|
/ @ref MeshTools::removeDuplicatesFuzzyIndexedInPlace() variants if there's an |
|
existing buffer in the first place. |
|
|
|
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: |
|
|
|
@snippet MeshTools.cpp meshtools-removeduplicates-position-only |
|
|
|
@todoc Fix duplicate / duplicateInto to not need those massive casts, ffs |
|
|
|
The `positionIndices` are made in two steps instead of using |
|
@ref MeshTools::removeDuplicatesIndexedInPlace() because the `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: |
|
|
|
@snippet MeshTools.cpp meshtools-removeduplicates-position-only-copy |
|
|
|
@section meshtools-bounding-volume Bounding volume calculation |
|
|
|
The @ref MeshTools::boundingRange() and |
|
@ref MeshTools::boundingSphereBouncingBubble() utilities can be used to |
|
calculate a bounding volume for a given list of vertex positions, for example |
|
to use for culling. Because their output is just a single value, they take a |
|
position view directly and don't have any convenience variant operating on a |
|
@ref Trade::MeshData. The most straightforward way is to pass |
|
@ref Trade::MeshData::positions3DAsArray() to them, see the |
|
@ref Trade-MeshData-access "MeshData data access documentation" for more |
|
details and alternative approaches that don't allocate a temporary array. |
|
|
|
@section meshtools-helpers Memory ownership helpers |
|
|
|
Much like all other heavier data structures in Magnum, a @ref Trade::MeshData |
|
is move-only, to prevent accidental copies when passing it around. If a copy is |
|
desirable and it cannot be made as a side effect of some other operation, |
|
@ref MeshTools::copy() can be used. |
|
|
|
Another use case for it is creating a self-contained @ref Trade::MeshData |
|
instance --- in some cases, such as for example with @ref Primitives::cubeSolid() |
|
or with memory-mapped files, you may get back a @ref Trade::MeshData that |
|
references external data, instead of owning them, to avoid unnecessary copies. |
|
Such instances usually cannot be modified in-place and one has to ensure that |
|
the memoory they reference stays in scope. Calling @ref MeshTools::copy() on |
|
such an instance makes a self-contained copy that owns the data and can be |
|
modified. For example, turning the cube primitive into a skybox with normals |
|
flipped inwards: |
|
|
|
@snippet MeshTools.cpp meshtools-copy |
|
|
|
An inverse to @ref MeshTools::copy() is @ref MeshTools::reference(). It makes a |
|
non-owning reference to data contained in another @ref Trade::MeshData. It's |
|
mainly useful for tool internals, for example to implement common handling for |
|
@cpp const Trade::MeshData& @ce and @cpp Trade::MeshData& @ce. |
|
|
|
*/ |
|
}
|
|
|