diff --git a/doc/features.dox b/doc/features.dox index ae452d3ca..e8a6cf418 100644 --- a/doc/features.dox +++ b/doc/features.dox @@ -207,6 +207,7 @@ function. - @subpage opengl-wrapping --- @copybrief opengl-wrapping - @subpage vulkan-wrapping --- @copybrief vulkan-wrapping - @subpage shaders --- @copybrief shaders +- @subpage meshtools --- @copybrief meshtools - @subpage scenegraph --- @copybrief scenegraph - @subpage debug-tools --- @copybrief debug-tools - @subpage ui --- @copybrief ui diff --git a/doc/meshtools.dox b/doc/meshtools.dox new file mode 100644 index 000000000..d8512a549 --- /dev/null +++ b/doc/meshtools.dox @@ -0,0 +1,411 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023, 2024 + Vladimír Vondruš + + 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) "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, 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&, 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) "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, 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) "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) "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, 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 + + + +@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&), +@ref MeshTools::removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D&, 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. + +*/ +} diff --git a/doc/namespaces.dox b/doc/namespaces.dox index 9ae4c363a..4648dd17c 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -411,7 +411,7 @@ See @ref building and @ref cmake for more information. /** @namespace Magnum::MeshTools @brief Mesh tools -Tools for generating, optimizing and cleaning meshes. +Tools for transforming, filtering, optimizing and merging meshes. This library is built if `MAGNUM_WITH_MESHTOOLS` is enabled when building Magnum. To use this library with CMake, request the `MeshTools` component of @@ -428,7 +428,7 @@ Note that functionality depending on @ref GL APIs is available only if Magnum is built with both `MAGNUM_WITH_GL` and `MAGNUM_TARGET_GL` enabled (which is done by default). -See @ref building and @ref cmake for more information. +See @ref building, @ref cmake and @ref meshtools for more information. */ /** @dir Magnum/Primitives diff --git a/doc/shaders.dox b/doc/shaders.dox index 1d75e4055..79750165d 100644 --- a/doc/shaders.dox +++ b/doc/shaders.dox @@ -177,6 +177,8 @@ changed between draws, it's possible to go even further by using @ref GL::MeshView instances onto a single @ref GL::Mesh instead of several different @ref GL::Mesh objects --- that way the attribute layout doesn't need to be updated and it's just submitting draws with different offsets and counts. +See @ref meshtools-concatenate for more information about tools for batching +meshes together. Finally, with mesh views and on platforms that support @gl_extension{ARB,shader_draw_parameters} from OpenGL 4.6 or the @gl_extension{ANGLE,multi_draw} / @webgl_extension{WEBGL,multi_draw} ES and diff --git a/doc/snippets/MeshTools-gl.cpp b/doc/snippets/MeshTools-gl.cpp index 544aa8fdb..9eb3ada46 100644 --- a/doc/snippets/MeshTools-gl.cpp +++ b/doc/snippets/MeshTools-gl.cpp @@ -30,9 +30,11 @@ #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" -#include "Magnum/Math/Vector3.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/Math/Color.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/Trade/MeshData.h" @@ -44,12 +46,55 @@ #define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ using namespace Magnum; +using namespace Magnum::Math::Literals; /* Make sure the name doesn't conflict with any other snippets to avoid linker warnings, unlike with `int main()` there now has to be a declaration to avoid -Wmisssing-prototypes */ void mainMeshToolsGL(); void mainMeshToolsGL() { +{ +struct MyShader: GL::AbstractShaderProgram { + MyShader& setColor(const Color3&) { + return *this; + } + MyShader& draw(GL::MeshView& mesh) { + GL::AbstractShaderProgram::draw(mesh); + return *this; + } +} shader; +/* [meshtools-concatenate] */ +Trade::MeshData sphere = DOXYGEN_ELLIPSIS(Trade::MeshData{{}, 0}); +Trade::MeshData cube = DOXYGEN_ELLIPSIS(Trade::MeshData{{}, 0}); +Trade::MeshData cylinder = DOXYGEN_ELLIPSIS(Trade::MeshData{{}, 0}); +Trade::MeshData primitives = MeshTools::concatenate({sphere, cube, cylinder}); + +GL::Mesh mesh = MeshTools::compile(primitives); +/* [meshtools-concatenate] */ + +/* [meshtools-concatenate-offsets] */ +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); +/* [meshtools-concatenate-offsets] */ +} + { Trade::MeshData meshData{MeshPrimitive::Lines, 5}; /* [compile-external] */ diff --git a/doc/snippets/MeshTools.cpp b/doc/snippets/MeshTools.cpp index 6051d2571..7429c7324 100644 --- a/doc/snippets/MeshTools.cpp +++ b/doc/snippets/MeshTools.cpp @@ -25,20 +25,27 @@ */ #include +#include #include +#include +#include #include "Magnum/Math/Color.h" #include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/Combine.h" #include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/Copy.h" #include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/Filter.h" #include "Magnum/MeshTools/FlipNormals.h" +#include "Magnum/MeshTools/GenerateIndices.h" #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Primitives/Cube.h" +#include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/Trade/MeshData.h" #ifdef MAGNUM_BUILD_DEPRECATED @@ -56,6 +63,153 @@ using namespace Magnum::Math::Literals; avoid -Wmisssing-prototypes */ void mainMeshTools(); void mainMeshTools() { +{ +/* [meshtools-interleave] */ +Trade::MeshData mesh = DOXYGEN_ELLIPSIS(Trade::MeshData{{}, 0}); + +mesh = MeshTools::interleave(std::move(mesh)); +/* [meshtools-interleave] */ + +/* [meshtools-compressindices] */ +if(mesh.isIndexed()) + mesh = MeshTools::compressIndices(std::move(mesh)); +/* [meshtools-compressindices] */ + +/* [meshtools-meshoptimizer] */ +PluginManager::Manager manager; +Containers::Pointer meshOptimizer = + manager.loadAndInstantiate("MeshOptimizerSceneConverter"); + +meshOptimizer->convertInPlace(mesh); +/* [meshtools-meshoptimizer] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +void performSomeProcessing(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); +/* [meshtools-generateindices] */ +Trade::MeshData indexed = MeshTools::generateIndices(mesh); + +performSomeProcessing(indexed.indices(), + indexed.positions3DAsArray()); +/* [meshtools-generateindices] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +/* [meshtools-transform] */ +mesh = MeshTools::transform3D(std::move(mesh), Matrix4::scaling({0.5f, 2.0f, 1.0f})); +/* [meshtools-transform] */ +} + +{ +/* [meshtools-interleave-insert] */ +Containers::ArrayView vertexColors = DOXYGEN_ELLIPSIS({}); + +Trade::MeshData coloredCube = MeshTools::interleave(Primitives::cubeSolid(), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, vertexColors} +}); +/* [meshtools-interleave-insert] */ +} + +{ +/* [meshtools-interleave-insert-placeholder] */ +Trade::MeshData coloredCube = MeshTools::interleave(Primitives::cubeSolid(), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, VertexFormat::Vector3, nullptr} +}); + +for(Color3& i: coloredCube.mutableAttribute(Trade::MeshAttribute::Color)) + i = DOXYGEN_ELLIPSIS({}); +/* [meshtools-interleave-insert-placeholder] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +/* [meshtools-duplicate-insert] */ +Trade::MeshAttribute VertexIdAttribute = Trade::meshAttributeCustom(DOXYGEN_ELLIPSIS(0)); + +Trade::MeshData vertexIdMesh = MeshTools::duplicate(mesh, { + Trade::MeshAttributeData{VertexIdAttribute, VertexFormat::UnsignedInt, nullptr} +}); + +UnsignedInt id = 0; +for(UnsignedInt& i: vertexIdMesh.mutableAttribute(VertexIdAttribute)) + i = id++; +/* [meshtools-duplicate-insert] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +/* [meshtools-filter] */ +Trade::MeshData positionsNormals = MeshTools::filterOnlyAttributes(mesh, { + Trade::MeshAttribute::Position, + Trade::MeshAttribute::Normal +}); +/* [meshtools-filter] */ + +/* [meshtools-filter-unsparse] */ +positionsNormals = MeshTools::interleave(positionsNormals, {}, {}); +/* [meshtools-filter-unsparse] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +/* [meshtools-removeduplicates] */ +Trade::MeshData deduplicated = MeshTools::removeDuplicatesFuzzy(mesh); +/* [meshtools-removeduplicates] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +/* [meshtools-meshoptimizer-simplify] */ +PluginManager::Manager manager; +Containers::Pointer meshOptimizer = + manager.loadAndInstantiate("MeshOptimizerSceneConverter"); +meshOptimizer->configuration().setValue("simplify", true); +meshOptimizer->configuration().setValue("simplifyTargetIndexCountThreshold", 0.1f); + +Containers::Optional simplified = meshOptimizer->convert(mesh); +/* [meshtools-meshoptimizer-simplify] */ +} + +{ +Trade::MeshData mesh{{}, 0}; +void performSimulation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); +/* [meshtools-removeduplicates-position-only] */ +Containers::Array positions = mesh.positions3DAsArray(); + +/* Deduplicate the positions and create a mapping array */ +Containers::Pair, std::size_t> out = + MeshTools::removeDuplicatesFuzzyInPlace( + Containers::arrayCast<2, Float>(stridedArrayView(positions))); +Containers::Array indexMapping = std::move(out.first()); +arrayResize(positions, out.second()); + +/* Combine the original index buffer with the mapping array */ +Containers::Array positionIndices = MeshTools::duplicate( + Containers::StridedArrayView1D{indexMapping}, + Containers::StridedArrayView1D{mesh.indicesAsArray()}); +/* [meshtools-removeduplicates-position-only] */ + +/* [meshtools-removeduplicates-position-only-copy] */ +performSimulation(positionIndices, positions); + +/* Copy updated positions back to the original locations in the mesh */ +MeshTools::duplicateInto( + Containers::StridedArrayView1D{indexMapping}, + Containers::StridedArrayView1D{positions}, + mesh.mutableAttribute(Trade::MeshAttribute::Position)); +/* [meshtools-removeduplicates-position-only-copy] */ +} + +{ +/* [meshtools-copy] */ +Trade::MeshData skybox = MeshTools::copy(Primitives::cubeSolid()); +MeshTools::flipNormalsInPlace(skybox.mutableIndices(), + skybox.mutableAttribute(Trade::MeshAttribute::Normal)); +/* [meshtools-copy] */ +} + { Trade::MeshData mesh{{}, 0}; /* [combineFaceAttributes] */ diff --git a/src/Magnum/MeshTools/BoundingVolume.h b/src/Magnum/MeshTools/BoundingVolume.h index ee8d30c71..11bbf8b9c 100644 --- a/src/Magnum/MeshTools/BoundingVolume.h +++ b/src/Magnum/MeshTools/BoundingVolume.h @@ -46,7 +46,7 @@ namespace Magnum { namespace MeshTools { Same as @ref Math::minmax(const Containers::StridedArrayView1D&). @see @ref Math::Intersection::rayRange(), @ref Math::Intersection::rangeFrustum(), - @ref Math::Intersection::rangeCone() + @ref Math::Intersection::rangeCone(), @ref meshtools-bounding-volume */ MAGNUM_MESHTOOLS_EXPORT Range3D boundingRange(const Containers::StridedArrayView1D& positions); @@ -67,7 +67,7 @@ problem, 2012, https://www.grin.com/document/204869*. @see @ref Math::Intersection::pointSphere(), @ref Math::Intersection::sphereFrustum(), @ref Math::Intersection::sphereCone(), - @ref Math::Intersection::sphereConeView() + @ref Math::Intersection::sphereConeView(), @ref meshtools-bounding-volume */ MAGNUM_MESHTOOLS_EXPORT Containers::Pair boundingSphereBouncingBubble(const Containers::StridedArrayView1D& positions); diff --git a/src/Magnum/MeshTools/Combine.h b/src/Magnum/MeshTools/Combine.h index 31e158dbb..131d2c1ad 100644 --- a/src/Magnum/MeshTools/Combine.h +++ b/src/Magnum/MeshTools/Combine.h @@ -94,7 +94,8 @@ using @ref removeDuplicatesInPlace() and then call this function. Index buffers and attributes in all meshes are expected to not have an implementation-specific format. @see @ref isMeshIndexTypeImplementationSpecific(), - @ref isVertexFormatImplementationSpecific() + @ref isVertexFormatImplementationSpecific(), + @ref meshtools-attributes-insert */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::Iterable& meshes); @@ -138,6 +139,7 @@ Same as with @ref combineFaceAttributes(const Trade::MeshData&, const Trade::Mes @p faceAttributes is expected to be interleaved. Note that offset-only @ref Trade::MeshAttributeData instances are not supported in the @p faceAttributes array. +@see @ref meshtools-attributes-insert */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, Containers::ArrayView faceAttributes); diff --git a/src/Magnum/MeshTools/Concatenate.h b/src/Magnum/MeshTools/Concatenate.h index df4e7812d..c6b4d2ae4 100644 --- a/src/Magnum/MeshTools/Concatenate.h +++ b/src/Magnum/MeshTools/Concatenate.h @@ -96,7 +96,7 @@ to compress it to a smaller type, if desired. @see @ref concatenateInto(), @ref isMeshIndexTypeImplementationSpecific(), @ref isVertexFormatImplementationSpecific(), @ref SceneTools::flattenMeshHierarchy2D(), - @ref SceneTools::flattenMeshHierarchy3D() + @ref SceneTools::flattenMeshHierarchy3D(), @ref meshtools-concatenate */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Containers::Iterable& meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); diff --git a/src/Magnum/MeshTools/Copy.h b/src/Magnum/MeshTools/Copy.h index 017924a18..45cead8b8 100644 --- a/src/Magnum/MeshTools/Copy.h +++ b/src/Magnum/MeshTools/Copy.h @@ -50,7 +50,8 @@ unchanged, the data layout isn't changed in any way. The resulting @ref Trade::DataFlag::Owned and @ref Trade::DataFlag::Mutable. Attributes that were offset-only before are kept offset-only, others have offsets recalculated against the newly-allocated vertex data. -@see @ref copy(Trade::MeshData&&), @ref reference(), @ref mutableReference() +@see @ref copy(Trade::MeshData&&), @ref reference(), @ref mutableReference(), + @ref meshtools-helpers */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData copy(const Trade::MeshData& mesh); @@ -69,7 +70,7 @@ ownership. The resulting data are always owned and mutable, the data layout isn't changed in any way. Attributes that were offset-only before are kept offset-only, others have offsets recalculated against the potentially-newly-allocated vertex data. -@see @ref reference(), @ref mutableReference() +@see @ref reference(), @ref mutableReference(), @ref meshtools-helpers */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData copy(Trade::MeshData&& mesh); @@ -81,7 +82,7 @@ The returned instance has empty @ref Trade::MeshData::indexDataFlags() and @ref Trade::MeshData::vertexDataFlags() and references attribute data from the @p mesh as well. The function performs no allocation or data copy. Use @ref copy() for an inverse operation. -@see @ref mutableReference() +@see @ref mutableReference(), @ref meshtools-helpers */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData reference(const Trade::MeshData& mesh); @@ -93,7 +94,7 @@ The returned instance has @ref Trade::MeshData::indexDataFlags() and @ref Trade::MeshData::vertexDataFlags() set to @ref Trade::DataFlag::Mutable. The function performs no allocation or data copy. Use @ref copy() for an inverse operation. Expects that @p mesh is mutable. -@see @ref reference() +@see @ref reference(), @ref meshtools-helpers */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData mutableReference(Trade::MeshData& mesh); diff --git a/src/Magnum/MeshTools/Filter.h b/src/Magnum/MeshTools/Filter.h index 2d0a962a7..59567c27c 100644 --- a/src/Magnum/MeshTools/Filter.h +++ b/src/Magnum/MeshTools/Filter.h @@ -52,7 +52,8 @@ This function only operates on the attribute metadata --- if you'd like to have the vertex data repacked to contain just the remaining attributes as well, pass the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags) "interleave()" without @ref InterleaveFlag::PreserveInterleavedAttributes set. -@see @ref reference() +@see @ref reference(), @ref filterOnlyAttributes(), + @ref filterExceptAttributes(), @ref meshtools-attributes-filter */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterAttributes(const Trade::MeshData& mesh, Containers::BitArrayView attributesToKeep); @@ -88,7 +89,8 @@ This function only operates on the attribute metadata --- if you'd like to have the vertex data repacked to contain just the remaining attributes as well, pass the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags) "interleave()" without @ref InterleaveFlag::PreserveInterleavedAttributes set. -@see @ref reference() +@see @ref reference(), @ref filterExceptAttributes(), + @ref meshtools-attributes-filter */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterOnlyAttributes(const Trade::MeshData& mesh, Containers::ArrayView attributes); @@ -136,6 +138,8 @@ This function only operates on the attribute metadata --- if you'd like to have the vertex mesh repacked to contain just the remaining attributes as well, pass the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags) "interleave()" without @ref InterleaveFlag::PreserveInterleavedAttributes set. +@see @ref reference(), @ref filterOnlyAttributes(), + @ref meshtools-attributes-filter */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterExceptAttributes(const Trade::MeshData& mesh, Containers::ArrayView attributes); diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index 3d293ad0a..dd4e40604 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -329,7 +329,8 @@ implementation-specific format, except for @p mesh attributes in case @p data is already interleaved, then the layout is untouched. @see @ref isInterleaved(), @ref isMeshIndexTypeImplementationSpecific(), @ref isVertexFormatImplementationSpecific(), - @ref Trade::MeshData::attributeData() + @ref Trade::MeshData::attributeData(), + @ref meshtools-attributes-insert */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& mesh, Containers::ArrayView extra = {}, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); @@ -386,6 +387,8 @@ implementation-specific format and none of them are offset-only assumed to not have an implementation-specific type. Returned instance vertex and index data flags have both @ref Trade::DataFlag::Mutable and @ref Trade::DataFlag::Owned, so mutable attribute access is guaranteed. +@see @ref meshtools-create, + @ref Trade-MeshData-populating "Populating a MeshData instance directly" */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(MeshPrimitive primitive, const Trade::MeshIndexData& indices, Containers::ArrayView attributes); diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index c170bbc17..251d86aca 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -69,7 +69,8 @@ for a variant that doesn't modify the input data in any way but instead returns an index array pointing to original data locations. Use @ref removeDuplicatesInPlaceInto() to place the indices into existing memory instead of allocating a new array. -@see @relativeref{Corrade,Containers::StridedArrayView::isContiguous()} +@see @relativeref{Corrade,Containers::StridedArrayView::isContiguous()}, + @ref meshtools-duplicates */ MAGNUM_MESHTOOLS_EXPORT Containers::Pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data); @@ -181,6 +182,7 @@ indices into existing memory instead of allocating a new array. If you want to remove duplicates in multiple incidental arrays, first remove duplicates in each array separately and then combine the resulting index arrays back into a single one using @ref combineIndexedAttributes(). +@see @ref meshtools-duplicates */ MAGNUM_MESHTOOLS_EXPORT Containers::Pair, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D& data, Float epsilon = Math::TypeTraits::epsilon()); @@ -317,7 +319,7 @@ In order to remove random padding values from the input and make the vertices suitable for fast in-place duplicate removal, this function unconditionally copies and interleaves the input vertex and index data. @see @ref isMeshIndexTypeImplementationSpecific(), - @ref isVertexFormatImplementationSpecific() + @ref isVertexFormatImplementationSpecific(), @ref meshtools-duplicates */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData removeDuplicates(const Trade::MeshData& mesh); @@ -331,6 +333,7 @@ on floating-point attributes. For attributes with a known range (such as @ref Trade::MeshAttribute::Normal being always @f$ [-1, 1] @f$ in each direction) the @p floatEpsilon / @p doubleEpsilon is scaled appropriately, otherwise it's scaled to calculated value range. +@see @ref meshtools-duplicates */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData removeDuplicatesFuzzy(const Trade::MeshData& mesh, Float floatEpsilon = Math::TypeTraits::epsilon(), Double doubleEpsilon = Math::TypeTraits::epsilon());