diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 1ba7d039d6..4121ed192d 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -343,7 +343,7 @@ for a high-level overview. - @cpp Buffer::invalidateData() @ce, @cpp Buffer::invalidateSubData() @ce and @cpp Renderer::resetNotificationStrategy() @ce functions are enabled on OpenGL ES as a no-op -- Added @ref std::vector overload of @ref MeshTools::combineIndexArrays() +- Added @ref std::vector overload of @cpp MeshTools::combineIndexArrays() @ce for greater runtime-usage flexibility - @ref Platform::Sdl2Application now defaults to non-resizable window, you can change the behavior using @ref Platform::Sdl2Application::Configuration::setWindowFlags() @@ -391,9 +391,9 @@ for a high-level overview. which filled @cpp Mesh @ce and @cpp Buffer @ce directly are deprecated as they had undesired side-effects in some cases, use the data-returning versions instead and then configure mesh and buffer manually -- @ref MeshTools::combineIndexedArrays() taking @ref std::tuple is +- @cpp MeshTools::combineIndexedArrays() @ce taking @ref std::tuple is deprecated, use version taking @ref std::pair instead -- @ref MeshTools::removeDuplicates() taking also list of indices is +- @cpp MeshTools::removeDuplicates() @ce taking also list of indices is deprecated, use the function in conjunction with @ref MeshTools::duplicate(). See function documentation for more information. - Parameter-less @cpp Mesh::draw() @ce and @cpp MeshView::draw() @ce are @@ -510,8 +510,8 @@ No dependency changes in this release. - New @ref Shaders::Generic class with common definitions, so you can configure mesh for the generic shader and render it with any other compatible shader -- Convenience @cpp hasNormals() @ce, @cpp hasTextureCoords2D() @ce functions to - @ref Trade::MeshData2D and @ref Trade::MeshData3D +- Convenience @cpp hasNormals() @ce, @cpp hasTextureCoords2D() @ce functions + in @cpp Trade::MeshData2D @ce and @cpp Trade::MeshData3D @ce - OpenGL ES 3.0 build now shares list of vendor extensions with OpenGL ES 2.0 build (i.e. only those extensions that aren't part of ES 3.0 are present in @cpp Extensions @ce) diff --git a/doc/changelog.dox b/doc/changelog.dox index 5634955948..0df47ac2c2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -49,6 +49,12 @@ See also: @ref Vector2s, @ref Vector3s, @ref Vector4s, @ref Color3h, @ref Color4h, @ref Color3us, @ref Color4us convenience typedefs for half-float, 8- and 16-bit integer vector and color types +- New @ref VertexFormat enum for vertex formats and related utilities +- New @ref MeshPrimitive::Instances, @ref MeshPrimitive::Faces and + @ref MeshPrimitive::Edges primitive types for describing per-instance, + per-face and per-edge data. Those don't map to any common GPU API, but can + be used by various importers to provide access to mesh data that is not + necessarily GPU-friendly. @subsubsection changelog-latest-new-audio Audio library @@ -106,6 +112,30 @@ See also: - Added @ref Math::reflect() and @ref Math::refract() (see [mosra/magnum#420](https://github.com/mosra/magnum/pull/420)) +@subsubsection changelog-latest-new-meshtools MeshTools library + +- Added @ref MeshTools::compile(const Trade::MeshData&) operating on the new + @ref Trade::MeshData API +- New @ref MeshTools::isInterleaved() and @ref MeshTools::interleavedData() + utilities for checking if @ref Trade::MeshData is interleaved and for + getting an interleaved view +- Added @ref MeshTools::interleavedLayout() for convenient creation of an + interleaved mesh layout using the new @ref Trade::MeshData API +- Added @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView), + @ref MeshTools::duplicate(const Trade::MeshData&, Containers::ArrayView) + and @ref MeshTools::compressIndices(const Trade::MeshData&, MeshIndexType) + that work directly on the new @ref Trade::MeshData API +- Added @ref MeshTools::subdivideInPlace() for allocation-less mesh + subdivision +- New @ref MeshTools::removeDuplicatesInPlace() variant that works on + discrete data in addition to floating-point +- New @ref MeshTools::combineIndexedAttributes() tool for combining + differently indexed attributes into a single index buffer, and + @ref MeshTools::combineFaceAttributes() for converting per-face attributes + into per-vertex +- New @ref MeshTools::concatenate() and @ref MeshTools::concatenateInto() + tool for batching multiple generic meshes together + @subsubsection changelog-latest-new-platform Platform libraries - Cursor management using @ref Platform::Sdl2Application::setCursor(), @@ -152,17 +182,36 @@ See also: @subsubsection changelog-latest-new-trade Trade library +- A new, redesigned @ref Trade::MeshData class that allows much more flexible + access to vertex/index data without unnecessary allocations and data + conversions or copies. Importers expose it through the new + @ref Trade::AbstractImporter::mesh() family of APIs. - Ability to import image mip levels via an additional parameter in @ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D and 3D images +- The @ref Trade::AnimationData class received support for mutable data + access with new constructors and the + @ref Trade::AnimationData::mutableData() "mutableData()" and + @ref Trade::AnimationData::mutableTrack() "mutableTrack()" accessors. + Equivalent APIs are exposed in both @ref Trade::ImageData and + @ref Trade::MeshData as well. See @ref Trade-AnimationData-usage-mutable + for more information. +- New convenience @ref Trade::AbstractImporter::material(const std::string&) + etc. APIs allowing to directly get a data using a string name @subsubsection changelog-latest-new-vk Vk library - Updated Vulkan headers for version 1.2 +- Conversion of @ref VertexFormat values to the @type_vk{Format} enum using + @ref Vk::vkFormat(Magnum::VertexFormat) @subsection changelog-latest-changes Changes and improvements +- The @ref MeshPrimitive type can now store implementation-specific primitive + types similarly to @ref PixelFormat and the new @ref VertexFormat. + Implementation-specific types are then simply passed through in + @ref GL::meshPrimitive() and @ref Vk::vkPrimitiveTopology(). - The @ref PixelFormat and @ref CompressedPixelFormat enums can now be saved and retrieved from @ref Corrade::Utility::Configuration / @ref Corrade::Utility::Arguments @@ -205,8 +254,29 @@ See also: @subsubsection changelog-latest-changes-meshtools MeshTools library +- Added @ref MeshTools::compressIndices() that takes a + @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector + and additionally allows you to specify the smallest allowed type, offset to + apply to each index and is working with 8- and 16-byte index types as well +- Added @ref MeshTools::subdivide() that operates on a (growable) + @ref Corrade::Containers::Array instead of a @ref std::vector - Added @ref MeshTools::subdivideInPlace() that operates on a partially filled array view instead of a @ref std::vector +- Added @ref MeshTools::removeDuplicatesIndexedInPlace() that operates + in-place on an indexed array view and a STL-less + @ref MeshTools::removeDuplicatesInPlace() variant +- Added @ref MeshTools::duplicateInto() variants that take type-erased + 2D strided array views +- @ref MeshTools::flipNormalsInPlace() and @ref MeshTools::flipFaceWindingInPlace() + were renamed for clarity and now accept a + @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector, + additionally working on 8- and 16-byte index types as well +- @ref MeshTools::tipsifyInPlace() was renamed for clarity and now accepts a + @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector, + additionally working on 8- and 16-byte index types as well +- Added @ref MeshTools::generateSmoothNormals() and + @ref MeshTools::generateSmoothNormalsInto() variants taking type-erased + index arrays @subsubsection changelog-latest-changes-platform Platform libraries @@ -240,6 +310,11 @@ See also: plugin if you specify `--importer raw:<format>`; and save raw imported data instead of going through a converter plugin if you specify `--converter raw` +- New convenience @ref Trade::AnimationTrackData constructor taking a + templated @ref Animation::TrackView type, autodetecting value and result + @ref Trade::AnimationTrackType out of it +- New convenience @ref Trade::AnimationData constructors taking an + @ref std::initializer_list of @ref Trade::AnimationTrackData @subsection changelog-latest-buildsystem Build system @@ -298,8 +373,8 @@ See also: - It was not possible to override DPI scaling using @ref Platform::Sdl2Application::Configuration as command-line arguments always got a priority (see [mosra/magnum#416](https://github.com/mosra/magnum/issues/416)) -- Fixed an otherwise harmless OOB access in @ref MeshTools::tipsify() that - could trigger ASan or debug iterator errors +- Fixed an otherwise harmless OOB access in @ref MeshTools::tipsifyInPlace() + that could trigger ASan or debug iterator errors - With @ref Corrade/Utility/DebugStl.h not being included, @ref std::string instances could get accidentally printed as @ref ResourceKey instances. Added and explicit header dependency to avoid such cases. @@ -323,6 +398,15 @@ See also: @ref GL::Attribute::DataType::Half, @ref GL::DynamicAttribute::DataType::Half and @ref GL::PixelType::Half that are consistent with the @ref Half type used elsewhere. +- @cpp Trade::AbstractImporter::mesh2D() @ce, + @cpp Trade::MeshData2D @ce, @cpp Trade::MeshData3D @ce, + @cpp Trade::AbstractImporter::mesh2D() @ce, + @cpp Trade::AbstractImporter::mesh3D() @ce and related APIs are + deprecated in favor of @ref Trade::AbstractImporter::mesh() and the new + @ref Trade::MeshData API. For backwards compatibility, importers + implementing the new API have it exposed through @cpp mesh3D() @ce as well, + returning a subset of @ref Trade::MeshData functionality supported by the + old @cpp Trade::MeshData3D @ce APIs - @cpp Platform::GlfwApplication::Configuration::setCursorMode() @ce and related enum & getter are deprecated in favor of the new extended and more flexible @ref Platform::GlfwApplication::setCursor(). The setting didn't @@ -341,6 +425,26 @@ See also: @ref Text::FontConverterFeatures, @ref Trade::ImporterFeatures, @ref Trade::ImageConverterFeatures enum sets and their corresponding enums placed directly in the namespace to have them shorter and unambiguous +- @cpp MeshTools::compressIndices() @ce taking a @ref std::vector is + deprecated in favor of @ref MeshTools::compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) + and its 8- and 16-byte overloads +- @cpp MeshTools::flipNormals() @ce and @cpp MeshTools::flipFaceWinding() @ce + and @cpp MeshTools::tipsify() @ce are deprecated in favor of + @ref MeshTools::flipNormalsInPlace(), + @ref MeshTools::flipFaceWindingInPlace() and @ref MeshTools::tipsifyInPlace() + that accept a @ref Corrade::Containers::StridedArrayView instead of a + @ref std::vector and work with 8- and 16-byte index types as well. +- `Magnum/MeshTools/CombineIndexedArrays.h`, + @cpp MeshTools::combineIndexArrays() @ce and + @cpp MeshTools::combineIndexedArrays() @ce are deprecated in favor of a more + flexible @ref MeshTools::combineIndexedAttributes() in the + @ref Magnum/MeshTools/Combine.h header +- @cpp MeshTools::compressIndicesAs() @ce, @cpp MeshTools::duplicate() @ce, + @cpp MeshTools::removeDuplicates() @ce and @cpp MeshTools::subdivide() @ce + operating on a @ref std::vector are deprecated, use the STL-free + @ref MeshTools::compressIndices(), @ref MeshTools::duplicate(), + @ref MeshTools::removeDuplicatesInPlace() and @ref MeshTools::subdivide() / + @ref MeshTools::subdivideInPlace() overloads instead @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs @@ -348,12 +452,22 @@ See also: @ref Animation library was changed to allow mutable access to the keys & values it references. Existing code needs to be changed to say @cpp TrackView @ce instead of @cpp TrackView @ce. + Following this change, @ref Trade::AnimationData now also return instances + with @cpp const @ce types and the non-const + @ref Trade::AnimationData::data() was renamed to + @ref Trade::AnimationData::mutableData() "mutableData()". - The 4-argument @ref GL::DynamicAttribute constructor was not marked as @cpp explicit @ce by mistake, it's done now to enforce readability in long expressions. - The @ref Magnum/Math/FunctionsBatch.h header is no longer included from @ref Magnum/Math/Functions.h for backwards compatibility in order to speed up compile times. +- @ref Trade::AnimationTrackData constructors are now explicit as that + enforces better readability in long initializer expressions +- Non-const @ref Trade::ImageData::data() and @ref Trade::ImageData::pixels() + were renamed to @ref Trade::ImageData::mutableData() and + @ref Trade::ImageData::mutablePixels() to follow the new + @ref Trade::MeshData API and similar changes in @ref Trade::AnimationData. - @ref Platform::GlfwApplication::setMinWindowSize() / @ref Platform::GlfwApplication::setMaxWindowSize() and equivalent APIs in @ref Platform::Sdl2Application now premultiply the value with @@ -381,6 +495,17 @@ See also: @ref Shaders::Phong::bindDiffuseTexture(), @ref Shaders::Phong::bindSpecularTexture() and @ref Shaders::Phong::bindTextures() instead +- @ref MeshPrimitive is now four bytes instead of one, to allow wrapping + implementation-specific values using @ref meshPrimitiveWrap() and + @ref meshPrimitiveUnwrap() +- @ref MeshPrimitive and @ref MeshIndexType now reserve the zero value to + indicate an invalid primitive / type, better catching accidentally + forgotten initialization. Valid code shouldn't be affected by this change, + but broken code that seemingly worked before might start throwing + assertions now. In contrast, @ref SamplerFilter, @ref SamplerMipmap and + @ref SamplerWrapping keep the zero value as a reasonable default. This + follows a similar change done for @ref PixelFormat and + @ref CompressedPixelFormat in 2019.10. @subsection changelog-latest-documentation Documentation @@ -632,8 +757,8 @@ Released 2019-10-24, tagged as location - @ref MeshTools::generateSmoothNormals() for generating weighted smooth normals of indexed meshes (see [mosra/magnum#229](https://github.com/mosra/magnum/pull/229)) -- @ref MeshTools::compile(const Trade::MeshData3D&, CompileFlags) now accepts - optional flags to control normal generation +- @cpp MeshTools::compile(const Trade::MeshData3D&, CompileFlags) @ce now + accepts optional flags to control normal generation @subsubsection changelog-2019-10-new-platform Platform libraries @@ -1019,7 +1144,7 @@ Released 2019-10-24, tagged as - Reading of float textures on ES3 contexts using @ref DebugTools::textureSubImage() returned a zero-sized image by accident - @ref MeshTools::compile() was producing an incorrect mesh when - @ref Trade::MeshData3D had both colors and texture coordinates + @cpp Trade::MeshData @ce had both colors and texture coordinates - Properly zero-initializing the UTF-8 buffer in @ref Platform::GlfwApplication::textInputEvent() (see [mosra/magnum#324](https://github.com/mosra/magnum/pull/324)) @@ -1425,9 +1550,9 @@ Released 2019-02-04, tagged as @ref GL::OpenGLTester library instead. Note that the deprecated `Magnum/OpenGLTester.h` header is still present, along with all other deprecated GL-specific headers and APIs in the project root. -- Removed five-argument @ref Trade::MeshData2D and six-argument - @ref Trade::MeshData3D constructors that were deprecated since - February 2017. Use the full six/seven-argument versions instead. +- Removed five-argument @cpp Trade::MeshData2D @ce and six-argument + @cpp Trade::MeshData3D @ce constructors that were deprecated since February + 2017. Use the full six/seven-argument versions instead. - Removed @ref Platform application constructors taking @cpp nullptr @ce, deprecated in June 2016 for windowless apps and in March 2017 for windowed apps. Use constructors taking the @ref NoCreate tag instead. @@ -1927,8 +2052,8 @@ Released 2018-10-23, tagged as clearer naming - @ref MeshTools::compile() taking a @ref GL::BufferUsage and returning a tuple was deprecated, use the simpler version taking just - @ref Trade::MeshData2D / @ref Trade::MeshData3D and directly returning a - @ref GL::Mesh instead + @cpp Trade::MeshData2D @ce / @cpp Trade::MeshData3D @ce and directly + returning a @ref GL::Mesh instead - `Shaders::VertexColor::Color` is deprecated, use the direct @ref Shaders::VertexColor::Color3 or @ref Shaders::VertexColor::Color4 alternatives instead @@ -2338,9 +2463,8 @@ Released 2018-05-01, tagged as In particular, @ref GL::Mesh::primitive() now returns @ref GL::MeshPrimitive instead of @ref Magnum::MeshPrimitive, code depending on the return type being implicitly convertible to - @ref Magnum::MeshPrimitive may break. IN all other cases, - @ref Trade::MeshData2D::primitive() "Trade::MeshData*D::primitive()" etc. - returns @ref Magnum::MeshPrimitive. + @ref Magnum::MeshPrimitive may break. In all other cases, + `Trade::MeshData*D::primitive()` etc. returns @ref Magnum::MeshPrimitive. - Configuration value reader/writers are now for only @ref Magnum::MeshPrimitive and @ref Magnum::MeshIndexType, not for @ref GL::MeshPrimitive or @ref GL::MeshIndexType @@ -2775,7 +2899,8 @@ a high-level overview. - New @ref magnum-imageconverter "magnum-imageconverter" utility - Initial implementation of @ref Trade::CameraData and @ref Trade::LightData -- Vertex color support in @ref Trade::MeshData2D and @ref Trade::MeshData3D +- Vertex color support in @cpp Trade::MeshData2D @ce and + @cpp Trade::MeshData3D @ce - @ref Trade::AbstractImageConverter member functions were changed to non-@cpp const @ce to make implementation of complex converter plugins possible - New @ref Trade::AbstractImageConverter::exportToCompressedImage() plugin @@ -3074,8 +3199,8 @@ a high-level overview. - `Math::normalize()` and `Math::denormalize()` had confusing naming and thus are deprecated, use @ref Math::pack() and @ref Math::unpack() from the @ref Magnum/Math/Packing.h header instead -- @ref Trade::MeshData2D and @ref Trade::MeshData3D constructors without the - `colors` parameter are deprecated, use the full ones instead +- @cpp Trade::MeshData2D @ce and @cpp Trade::MeshData3D @ce constructors + without the `colors` parameter are deprecated, use the full ones instead - @cpp Shaders::Generic::Color @ce vertex attribute implicit constructor is deprecated, use a constructor with explicit component count instead - The bundled @ref std::optional implementation was causing serious conflicts @@ -3105,9 +3230,10 @@ a high-level overview. - Removed deprecated `*Texture::maxLayers()` functions, use @ref GL::Shader::maxCombinedTextureImageUnits() "Shader::maxCombinedTextureImageUnits()" instead -- Removed deprecated @ref MeshTools::combineIndexedArrays(), +- Removed deprecated @cpp MeshTools::combineIndexedArrays() @ce, @ref MeshTools::compressIndices(), @ref MeshTools::interleave() and - @ref MeshTools::removeDuplicates() overloads, use the general ones instead + @cpp MeshTools::removeDuplicates() @ce overloads, use the general ones + instead - Removed deprecated `Mesh*::set*{Range,Count}()` functions, use @ref GL::Mesh::setCount() "Mesh*::setCount()" and @ref GL::MeshView::setIndexRange() "MeshView::setIndexRange()" instead diff --git a/doc/developers.dox b/doc/developers.dox index c1ae084d10..138fb391b3 100644 --- a/doc/developers.dox +++ b/doc/developers.dox @@ -407,7 +407,7 @@ in inverse --- but usually @ref developers-deprecation "deprecate first". - run [doxygen.py](http://mcss.mosra.cz/doxygen/) on `Doxyfile-mcss` and verify there are no new warnings - eyeball the relevant docs and fix suspicious things -10. Push to a temporary branch (e.g., `next`) +10. Push to a temporary branch (e.g., `next`) 11. Iterate until the CIs are green 12. Merge to `master` 13. If possible, trigger builds of dependent projects (where they are still @@ -488,6 +488,61 @@ in inverse --- but usually @ref developers-deprecation "deprecate first". 8. If possible, trigger builds of dependent projects and verify they are still green (or wait for the scheduled builds) +@section developers-adding-attribute Checklist for adding a new mesh attribute + +1. Extend @ref Trade::MeshAttribute with the new entry +2. Add a corresponding reserved type to @ref Shaders::Generic, if not there + already + - Also update `src/Magnum/Shaders/generic.glsl` with the reserved ID +3. Update the type assertion in the @ref Trade::MeshAttributeData constructor + to account for the new type +4. Add a pair of convenience getters to @ref Trade::MeshData similar to e.g. + @ref Trade::MeshData::normalsInto() / @ref Trade::MeshData::normalsAsArray() + with a type that's the same as the one used in the @ref Shaders::Generic + definition, test that it does the right thing for every supported type +5. Update @ref Trade::operator<<(Debug&, MeshAttribute) for the new entry +6. Update @ref MeshTools::compile() to recognize the new attribute. If there + is already a builtin shader capable of using this attribute, add new test + to `MeshToolsCompileGLTest`. +7. Push to a temporary branch (e.g., `next`) +8. Iterate until the CIs are green +9. Merge to `master` + +@section developers-adding-vertex-format Checklist for adding a new vertex format + +1. Extend @ref VertexFormat with the new entry, if it's not there already, + document mapping to GL, Vulkan, D3D and Metal (if exists) +2. Update docs of @ref Trade::MeshAttribute to mention where the format can be + newly used +3. Appropriately relax the assertion in the @ref Trade::MeshAttributeData + constructor +3. Extend `Trade::Implementation::vertexFormatFor()`, add a mapping between + this entry and a C++ type, optionally also + `isVertexFormatCompatibleWithAttribute()` if there's more than one entry + corresponding to a particular C++ type. If the mapping is unconventional, + be sure to mention it in the + @ref Trade::MeshAttributeData::MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + constructor docs. +4. Update corresponding `Trade::MeshData::*Into()` convenience getters to + ensure they can handle this type +5. Update `src/Magnum/Implementation/vertexFormatMapping.hpp` and + `src/Magnum/Vk/Implementation/vertexFormatMapping.hpp` with the new entry +6. Update @ref vertexFormatSize(), @ref vertexFormatComponentFormat(), + @ref vertexFormatComponentCount() and @ref isVertexFormatNormalized() to + handle this format +7. Update the @ref GL::hasVertexFormat() utility and + @ref GL::DynamicAttribute::DynamicAttribute(Kind, UnsignedInt, VertexFormat) + constructor to provide mapping of the new type to GL; add a test for the + new type, if it's special in some way, otherwise the all-catching loop will + check it +8. Update @ref MeshTools::compile() to recognize the new type (if anything + extra needs to be done, usually doesn't as everything is handled by + @ref GL::DynamicAttribute already); add corresponding new test(s) to + `MeshToolsCompileGLTest`. +9. Push to a temporary branch (e.g., `next`) +10. Iterate until the CIs are green +11. Merge to `master` + @section developers-gl-extensions Checklist for adding / removing GL versions and extensions 1. Install [flextGL](https://github.com/mosra/flextgl) diff --git a/doc/generated/primitives.cpp b/doc/generated/primitives.cpp index ce45693428..4e0033362b 100644 --- a/doc/generated/primitives.cpp +++ b/doc/generated/primitives.cpp @@ -76,8 +76,7 @@ #include #include #include -#include -#include +#include #include using namespace Magnum; @@ -93,46 +92,46 @@ struct PrimitiveVisualizer: Platform::WindowlessApplication { int exec() override; - std::pair axis2D(); - std::pair axis3D(); - - std::pair capsule2DWireframe(); - std::pair circle2DWireframe(); - std::pair crosshair2D(); - std::pair line2D(); - std::pair squareWireframe(); - - std::pair capsule3DWireframe(); - std::pair circle3DWireframe(); - std::pair crosshair3D(); - std::pair coneWireframe(); - std::pair cubeWireframe(); - std::pair cylinderWireframe(); - std::pair grid3DWireframe(); - std::pair line3D(); - std::pair planeWireframe(); - std::pair uvSphereWireframe(); - - std::pair circle2DSolid(); - std::pair squareSolid(); - - std::pair capsule3DSolid(); - std::pair circle3DSolid(); - std::pair coneSolid(); - std::pair cubeSolid(); - std::pair cylinderSolid(); - std::pair grid3DSolid(); - std::pair icosphereSolid(); - std::pair planeSolid(); - std::pair uvSphereSolid(); - - std::pair gradient2D(); - std::pair gradient2DHorizontal(); - std::pair gradient2DVertical(); - - std::pair gradient3D(); - std::pair gradient3DHorizontal(); - std::pair gradient3DVertical(); + std::pair axis2D(); + std::pair axis3D(); + + std::pair capsule2DWireframe(); + std::pair circle2DWireframe(); + std::pair crosshair2D(); + std::pair line2D(); + std::pair squareWireframe(); + + std::pair capsule3DWireframe(); + std::pair circle3DWireframe(); + std::pair crosshair3D(); + std::pair coneWireframe(); + std::pair cubeWireframe(); + std::pair cylinderWireframe(); + std::pair grid3DWireframe(); + std::pair line3D(); + std::pair planeWireframe(); + std::pair uvSphereWireframe(); + + std::pair circle2DSolid(); + std::pair squareSolid(); + + std::pair capsule3DSolid(); + std::pair circle3DSolid(); + std::pair coneSolid(); + std::pair cubeSolid(); + std::pair cylinderSolid(); + std::pair grid3DSolid(); + std::pair icosphereSolid(); + std::pair planeSolid(); + std::pair uvSphereSolid(); + + std::pair gradient2D(); + std::pair gradient2DHorizontal(); + std::pair gradient2DVertical(); + + std::pair gradient3D(); + std::pair gradient3DHorizontal(); + std::pair gradient3DVertical(); }; namespace { @@ -191,7 +190,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -210,7 +209,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -235,7 +234,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -265,7 +264,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -299,18 +298,12 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); - /* TODO: use MeshTools::compile() and MeshVisualizer2D once it exists */ - GL::Buffer vertices; - vertices.setData(data->positions(0), GL::BufferUsage::StaticDraw); - GL::Mesh mesh; - mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer::Position{Shaders::MeshVisualizer::Position::Components::Two}) - .setCount(data->positions(0).size()) - .setPrimitive(data->primitive()); - - mesh.draw(flat) + /* TODO: use MeshVisualizer2D once it exists */ + MeshTools::compile(*data) + .draw(flat) .draw(wireframe2D); GL::AbstractFramebuffer::blit(multisampleFramebuffer, framebuffer, framebuffer.viewport(), GL::FramebufferBlit::Color); @@ -349,7 +342,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data) @@ -372,7 +365,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data) @@ -395,7 +388,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data) @@ -411,11 +404,11 @@ int PrimitiveVisualizer::exec() { return 0; } -std::pair PrimitiveVisualizer::axis2D() { +std::pair PrimitiveVisualizer::axis2D() { return {Primitives::axis2D(), "axis2d.png"}; } -std::pair PrimitiveVisualizer::gradient2D() { +std::pair PrimitiveVisualizer::gradient2D() { return {Primitives::gradient2D({1.0f, -2.0f}, 0x2f83cc_srgbf, {-1.0f, 2.0f}, 0x3bd267_srgbf), "gradient2d.png"}; } @@ -426,142 +419,147 @@ namespace { const Color3 Gradient80Percent = Math::lerp(0x2f83cc_srgbf, 0x3bd267_srgbf, 0.8f); } -std::pair PrimitiveVisualizer::gradient2DHorizontal() { +std::pair PrimitiveVisualizer::gradient2DHorizontal() { return {Primitives::gradient2DHorizontal(Gradient20Percent, Gradient80Percent), "gradient2dhorizontal.png"}; } -std::pair PrimitiveVisualizer::gradient2DVertical() { +std::pair PrimitiveVisualizer::gradient2DVertical() { /* End colors are 20%/80% blends of the above to match the range */ return {Primitives::gradient2DVertical(Gradient20Percent, Gradient80Percent), "gradient2dvertical.png"}; } -std::pair PrimitiveVisualizer::axis3D() { +std::pair PrimitiveVisualizer::axis3D() { return {Primitives::axis3D(), "axis3d.png"}; } -std::pair PrimitiveVisualizer::gradient3D() { +std::pair PrimitiveVisualizer::gradient3D() { return {Primitives::gradient3D({1.0f, -2.0f, -1.5f}, 0x2f83cc_srgbf, {-1.0f, 2.0f, -1.5f}, 0x3bd267_srgbf), "gradient3d.png"}; } -std::pair PrimitiveVisualizer::gradient3DHorizontal() { +std::pair PrimitiveVisualizer::gradient3DHorizontal() { return {Primitives::gradient3DHorizontal(Gradient20Percent, Gradient80Percent), "gradient3dhorizontal.png"}; } -std::pair PrimitiveVisualizer::gradient3DVertical() { +std::pair PrimitiveVisualizer::gradient3DVertical() { return {Primitives::gradient3DVertical(Gradient20Percent, Gradient80Percent), "gradient3dvertical.png"}; } -std::pair PrimitiveVisualizer::capsule2DWireframe() { - auto capsule = Primitives::capsule2DWireframe(8, 1, 0.75f); - MeshTools::transformPointsInPlace(Matrix3::scaling(Vector2{0.75f}), capsule.positions(0)); +std::pair PrimitiveVisualizer::capsule2DWireframe() { + Trade::MeshData capsule = Primitives::capsule2DWireframe(8, 1, 0.75f); + MeshTools::transformPointsInPlace(Matrix3::scaling(Vector2{0.75f}), + capsule.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(capsule), "capsule2dwireframe.png"}; } -std::pair PrimitiveVisualizer::circle2DWireframe() { +std::pair PrimitiveVisualizer::circle2DWireframe() { return {Primitives::circle2DWireframe(32), "circle2dwireframe.png"}; } -std::pair PrimitiveVisualizer::crosshair2D() { +std::pair PrimitiveVisualizer::crosshair2D() { return {Primitives::crosshair2D(), "crosshair2d.png"}; } -std::pair PrimitiveVisualizer::line2D() { - auto line = Primitives::line2D(); - MeshTools::transformPointsInPlace(Matrix3::translation(Vector2::xAxis(-1.0f))*Matrix3::scaling(Vector2::xScale(2.0f)), line.positions(0)); +std::pair PrimitiveVisualizer::line2D() { + Trade::MeshData line = Primitives::line2D(); + MeshTools::transformPointsInPlace(Matrix3::translation(Vector2::xAxis(-1.0f))*Matrix3::scaling(Vector2::xScale(2.0f)), + line.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(line), "line2d.png"}; } -std::pair PrimitiveVisualizer::squareWireframe() { +std::pair PrimitiveVisualizer::squareWireframe() { return {Primitives::squareWireframe(), "squarewireframe.png"}; } -std::pair PrimitiveVisualizer::capsule3DWireframe() { - auto capsule = Primitives::capsule3DWireframe(8, 1, 16, 1.0f); - MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), capsule.positions(0)); +std::pair PrimitiveVisualizer::capsule3DWireframe() { + Trade::MeshData capsule = Primitives::capsule3DWireframe(8, 1, 16, 1.0f); + MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), + capsule.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(capsule), "capsule3dwireframe.png"}; } -std::pair PrimitiveVisualizer::circle3DWireframe() { +std::pair PrimitiveVisualizer::circle3DWireframe() { return {Primitives::circle3DWireframe(32), "circle3dwireframe.png"}; } -std::pair PrimitiveVisualizer::crosshair3D() { +std::pair PrimitiveVisualizer::crosshair3D() { return {Primitives::crosshair3D(), "crosshair3d.png"}; } -std::pair PrimitiveVisualizer::coneWireframe() { +std::pair PrimitiveVisualizer::coneWireframe() { return {Primitives::coneWireframe(32, 1.25f), "conewireframe.png"}; } -std::pair PrimitiveVisualizer::cubeWireframe() { +std::pair PrimitiveVisualizer::cubeWireframe() { return {Primitives::cubeWireframe(), "cubewireframe.png"}; } -std::pair PrimitiveVisualizer::cylinderWireframe() { +std::pair PrimitiveVisualizer::cylinderWireframe() { return {Primitives::cylinderWireframe(1, 32, 1.0f), "cylinderwireframe.png"}; } -std::pair PrimitiveVisualizer::grid3DWireframe() { +std::pair PrimitiveVisualizer::grid3DWireframe() { return {Primitives::grid3DWireframe({5, 3}), "grid3dwireframe.png"}; } -std::pair PrimitiveVisualizer::line3D() { - auto line = Primitives::line3D(); - MeshTools::transformPointsInPlace(Matrix4::translation(Vector3::xAxis(-1.0f))*Matrix4::scaling(Vector3::xScale(2.0f)), line.positions(0)); +std::pair PrimitiveVisualizer::line3D() { + Trade::MeshData line = Primitives::line3D(); + MeshTools::transformPointsInPlace(Matrix4::translation(Vector3::xAxis(-1.0f))*Matrix4::scaling(Vector3::xScale(2.0f)), + line.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(line), "line3d.png"}; } -std::pair PrimitiveVisualizer::planeWireframe() { +std::pair PrimitiveVisualizer::planeWireframe() { return {Primitives::planeWireframe(), "planewireframe.png"}; } -std::pair PrimitiveVisualizer::uvSphereWireframe() { +std::pair PrimitiveVisualizer::uvSphereWireframe() { return {Primitives::uvSphereWireframe(16, 32), "uvspherewireframe.png"}; } -std::pair PrimitiveVisualizer::circle2DSolid() { +std::pair PrimitiveVisualizer::circle2DSolid() { return {Primitives::circle2DSolid(16), "circle2dsolid.png"}; } -std::pair PrimitiveVisualizer::squareSolid() { +std::pair PrimitiveVisualizer::squareSolid() { return {Primitives::squareSolid(), "squaresolid.png"}; } -std::pair PrimitiveVisualizer::capsule3DSolid() { - auto capsule = Primitives::capsule3DSolid(4, 1, 12, 0.75f); - MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), capsule.positions(0)); +std::pair PrimitiveVisualizer::capsule3DSolid() { + Trade::MeshData capsule = Primitives::capsule3DSolid(4, 1, 12, 0.75f); + MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), + capsule.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(capsule), "capsule3dsolid.png"}; } -std::pair PrimitiveVisualizer::circle3DSolid() { +std::pair PrimitiveVisualizer::circle3DSolid() { return {Primitives::circle3DSolid(16), "circle3dsolid.png"}; } -std::pair PrimitiveVisualizer::coneSolid() { +std::pair PrimitiveVisualizer::coneSolid() { return {Primitives::coneSolid(1, 12, 1.25f, Primitives::ConeFlag::CapEnd), "conesolid.png"}; } -std::pair PrimitiveVisualizer::cubeSolid() { +std::pair PrimitiveVisualizer::cubeSolid() { return {Primitives::cubeSolid(), "cubesolid.png"}; } -std::pair PrimitiveVisualizer::cylinderSolid() { +std::pair PrimitiveVisualizer::cylinderSolid() { return {Primitives::cylinderSolid(1, 12, 1.0f, Primitives::CylinderFlag::CapEnds), "cylindersolid.png"}; } -std::pair PrimitiveVisualizer::grid3DSolid() { +std::pair PrimitiveVisualizer::grid3DSolid() { return {Primitives::grid3DSolid({5, 3}), "grid3dsolid.png"}; } -std::pair PrimitiveVisualizer::icosphereSolid() { +std::pair PrimitiveVisualizer::icosphereSolid() { return {Primitives::icosphereSolid(1), "icospheresolid.png"}; } -std::pair PrimitiveVisualizer::planeSolid() { +std::pair PrimitiveVisualizer::planeSolid() { return {Primitives::planeSolid(), "planesolid.png"}; } -std::pair PrimitiveVisualizer::uvSphereSolid() { +std::pair PrimitiveVisualizer::uvSphereSolid() { return {Primitives::uvSphereSolid(8, 16), "uvspheresolid.png"}; } diff --git a/doc/generated/shaders.cpp b/doc/generated/shaders.cpp index 100e372434..cf488d12dc 100644 --- a/doc/generated/shaders.cpp +++ b/doc/generated/shaders.cpp @@ -66,8 +66,7 @@ #include #include #include -#include -#include +#include #include using namespace Magnum; @@ -192,22 +191,22 @@ std::string ShaderVisualizer::flat() { } std::string ShaderVisualizer::vertexColor() { - Trade::MeshData3D sphere = Primitives::uvSphereSolid(32, 64); + Trade::MeshData sphere = Primitives::uvSphereSolid(32, 64); /* Color vertices nearest to given position */ auto target = Vector3{2.0f, 2.0f, 7.0f}.normalized(); std::vector colors; - colors.reserve(sphere.positions(0).size()); - for(Vector3 position: sphere.positions(0)) + colors.reserve(sphere.vertexCount()); + for(Vector3 position: sphere.attribute(Trade::MeshAttribute::Position)) colors.push_back(Color3::fromHsv({Math::lerp(240.0_degf, 420.0_degf, Math::max(1.0f - (position - target).length(), 0.0f)), 0.85f, 0.666f})); GL::Buffer vertices, indices; - vertices.setData(MeshTools::interleave(sphere.positions(0), colors), GL::BufferUsage::StaticDraw); - indices.setData(sphere.indices(), GL::BufferUsage::StaticDraw); + vertices.setData(MeshTools::interleave(sphere.attribute(Trade::MeshAttribute::Position), colors), GL::BufferUsage::StaticDraw); + indices.setData(sphere.indices(), GL::BufferUsage::StaticDraw); GL::Mesh mesh; mesh.setPrimitive(GL::MeshPrimitive::Triangles) - .setCount(sphere.indices().size()) + .setCount(sphere.indexCount()) .addVertexBuffer(vertices, 0, Shaders::VertexColor3D::Position{}, Shaders::VertexColor3D::Color3{}) diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index b6533726b0..3dfb6f426b 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include /* for std::tie() :( */ #include #include #include @@ -52,7 +53,7 @@ #include "Magnum/Primitives/Cube.h" #include "Magnum/Primitives/Plane.h" #include "Magnum/Shaders/Phong.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) #include "Magnum/GL/SampleQuery.h" @@ -1019,16 +1020,17 @@ mesh.setPrimitive(MeshPrimitive::Triangles) { /* [Mesh-interleaved] */ /* Non-indexed primitive with positions and normals */ -Trade::MeshData3D plane = Primitives::planeSolid(); +Trade::MeshData plane = Primitives::planeSolid(); /* Fill a vertex buffer with interleaved position and normal data */ GL::Buffer buffer; -buffer.setData(MeshTools::interleave(plane.positions(0), plane.normals(0)), GL::BufferUsage::StaticDraw); +buffer.setData(MeshTools::interleave(plane.positions3DAsArray(), + plane.normalsAsArray())); /* Configure the mesh, add the vertex buffer */ GL::Mesh mesh; mesh.setPrimitive(plane.primitive()) - .setCount(plane.positions(0).size()) + .setCount(plane.vertexCount()) .addVertexBuffer(buffer, 0, Shaders::Phong::Position{}, Shaders::Phong::Normal{}); /* [Mesh-interleaved] */ } @@ -1048,14 +1050,14 @@ Vector3 positions[240]{ // ... }; GL::Buffer vertexBuffer; -vertexBuffer.setData(positions, GL::BufferUsage::StaticDraw); +vertexBuffer.setData(positions); /* Fill index buffer with index data */ UnsignedByte indices[75]{ // ... }; GL::Buffer indexBuffer; -indexBuffer.setData(indices, GL::BufferUsage::StaticDraw); +indexBuffer.setData(indices); /* Configure the mesh, add both vertex and index buffer */ GL::Mesh mesh; @@ -1069,28 +1071,28 @@ mesh.setPrimitive(MeshPrimitive::Triangles) { /* [Mesh-indexed-tools] */ // Indexed primitive -Trade::MeshData3D cube = Primitives::cubeSolid(); +Trade::MeshData cube = Primitives::cubeSolid(); // Fill vertex buffer with interleaved position and normal data GL::Buffer vertexBuffer; -vertexBuffer.setData(MeshTools::interleave(cube.positions(0), cube.normals(0)), GL::BufferUsage::StaticDraw); +vertexBuffer.setData(MeshTools::interleave(cube.positions3DAsArray(), + cube.normalsAsArray())); // Compress index data Containers::Array indexData; MeshIndexType indexType; -UnsignedInt indexStart, indexEnd; -std::tie(indexData, indexType, indexStart, indexEnd) = MeshTools::compressIndices(cube.indices()); +std::tie(indexData, indexType) = MeshTools::compressIndices(cube.indices()); // Fill index buffer GL::Buffer indexBuffer; -indexBuffer.setData(indexData, GL::BufferUsage::StaticDraw); +indexBuffer.setData(indexData); // Configure the mesh, add both vertex and index buffer GL::Mesh mesh; mesh.setPrimitive(cube.primitive()) - .setCount(cube.indices().size()) + .setCount(cube.indexCount()) .addVertexBuffer(vertexBuffer, 0, Shaders::Phong::Position{}, Shaders::Phong::Normal{}) - .setIndexBuffer(indexBuffer, 0, indexType, indexStart, indexEnd); + .setIndexBuffer(indexBuffer, 0, indexType); /* [Mesh-indexed-tools] */ } diff --git a/doc/snippets/MagnumMeshTools-gl.cpp b/doc/snippets/MagnumMeshTools-gl.cpp index bf17e95e71..268041f98f 100644 --- a/doc/snippets/MagnumMeshTools-gl.cpp +++ b/doc/snippets/MagnumMeshTools-gl.cpp @@ -23,18 +23,73 @@ DEALINGS IN THE SOFTWARE. */ +#include /* for std::tie() :( */ +#include + #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" #include "Magnum/Math/Vector3.h" +#include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; int main() { { +Trade::MeshData meshData{MeshPrimitive::Lines, 5}; +/* [compile-external] */ +GL::Buffer indices, vertices; +indices.setData(meshData.indexData()); +vertices.setData(meshData.vertexData()); + +GL::Mesh mesh = MeshTools::compile(meshData, indices, vertices); +/* [compile-external] */ +} + +{ +Trade::MeshData meshData{MeshPrimitive::Lines, 5}; +Trade::MeshAttribute myCustomAttribute{}; +/* [compile-external-attributes] */ +GL::Buffer indices, vertices; +indices.setData(meshData.indexData()); +vertices.setData(meshData.vertexData()); + +/* Let compile() handle the usual attributes and configure custom ones after */ +GL::Mesh mesh = MeshTools::compile(meshData, std::move(indices), vertices); +mesh.addVertexBuffer(std::move(vertices), + meshData.attributeOffset(myCustomAttribute), + meshData.attributeStride(myCustomAttribute), + GL::DynamicAttribute{ + GL::DynamicAttribute::Kind::Generic, 7, + GL::DynamicAttribute::Components::One, + GL::DynamicAttribute::DataType::Float}); +/* [compile-external-attributes] */ +} + +{ +/* [compressIndices] */ +Containers::Array indices; + +Containers::Array indexData; +MeshIndexType indexType; +std::tie(indexData, indexType) = MeshTools::compressIndices(indices); + +GL::Buffer indexBuffer; +indexBuffer.setData(indexData); + +GL::Mesh mesh; +mesh.setCount(indices.size()) + .setIndexBuffer(indexBuffer, 0, indexType); /* [compressIndices] */ +} + +#ifdef MAGNUM_BUILD_DEPRECATED +{ +CORRADE_IGNORE_DEPRECATED_PUSH +/* [compressIndices-stl] */ std::vector indices; Containers::Array indexData; @@ -49,8 +104,10 @@ indexBuffer.setData(indexData, GL::BufferUsage::StaticDraw); GL::Mesh mesh; mesh.setCount(indices.size()) .setIndexBuffer(indexBuffer, 0, indexType, indexStart, indexEnd); -/* [compressIndices] */ +/* [compressIndices-stl] */ } +CORRADE_IGNORE_DEPRECATED_POP +#endif { struct MyShader { @@ -58,15 +115,16 @@ struct MyShader { typedef GL::Attribute<0, Vector2> TextureCoordinates; }; /* [interleave1] */ -std::vector positions; -std::vector textureCoordinates; +Containers::ArrayView positions; +Containers::ArrayView textureCoordinates; GL::Buffer vertexBuffer; -vertexBuffer.setData(MeshTools::interleave(positions, textureCoordinates), GL::BufferUsage::StaticDraw); +vertexBuffer.setData(MeshTools::interleave(positions, textureCoordinates)); GL::Mesh mesh; mesh.setCount(positions.size()) - .addVertexBuffer(vertexBuffer, 0, MyShader::Position{}, MyShader::TextureCoordinates{}); + .addVertexBuffer(vertexBuffer, 0, MyShader::Position{}, + MyShader::TextureCoordinates{}); /* [interleave1] */ } diff --git a/doc/snippets/MagnumMeshTools-stl.cpp b/doc/snippets/MagnumMeshTools-stl.cpp index 82bbf5a359..ba3c634d76 100644 --- a/doc/snippets/MagnumMeshTools-stl.cpp +++ b/doc/snippets/MagnumMeshTools-stl.cpp @@ -26,6 +26,7 @@ #include #include +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/GenerateNormals.h" using namespace Magnum; @@ -58,7 +59,7 @@ std::vector indices; std::vector positions; std::vector normals{positions.size()}; -MeshTools::generateSmoothNormalsInto(indices, positions, normals); +MeshTools::generateSmoothNormalsInto(indices, positions, normals); /* [generateSmoothNormalsInto] */ } diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index c57ee65b88..32a991c88e 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -24,20 +24,31 @@ */ #include "Magnum/Math/Color.h" -#include "Magnum/MeshTools/CombineIndexedArrays.h" +#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/FlipNormals.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/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS +#include "Magnum/MeshTools/CombineIndexedArrays.h" +#endif using namespace Magnum; using namespace Magnum::Math::Literals; int main() { +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH /* [combineIndexedArrays] */ std::vector vertexIndices; std::vector positions; @@ -51,15 +62,41 @@ std::vector indices = MeshTools::combineIndexedArrays( std::make_pair(std::cref(normalTextureIndices), std::ref(textureCoordinates)) ); /* [combineIndexedArrays] */ +CORRADE_IGNORE_DEPRECATED_POP +} +#endif + +{ +/* [compressIndices-offset] */ +Containers::ArrayView indices; +UnsignedInt offset = Math::min(indices); +std::pair, MeshIndexType> result = + MeshTools::compressIndices(indices, offset); + +// use `offset` to adjust vertex attribute offset … +/* [compressIndices-offset] */ +} + +{ +/* [concatenate-make-mutable] */ +/* Flip triangles on a cube primitive so it's counterclockwise from the inside + in order to render a cube map */ +Trade::MeshData mesh = MeshTools::concatenate(Primitives::cubeSolid()); +MeshTools::flipFaceWindingInPlace(mesh.mutableIndices()); +/* [concatenate-make-mutable] */ } +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH /* [compressIndicesAs] */ std::vector indices; Containers::Array indexData = MeshTools::compressIndicesAs(indices); /* [compressIndicesAs] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { /* [generateFlatNormals] */ @@ -76,25 +113,64 @@ Containers::Array normals = { /* [interleave2] */ -std::vector positions; -std::vector weights; -std::vector vertexColors; +Containers::ArrayView positions; +Containers::ArrayView weights; +Containers::ArrayView vertexColors; auto data = MeshTools::interleave(positions, weights, 2, vertexColors, 1); /* [interleave2] */ } { -/* [removeDuplicates1] */ -std::vector indices; -std::vector positions; +Trade::MeshData data{MeshPrimitive::Lines, 0}; +UnsignedInt vertexCount{}; +Containers::Array indexData; +/* [interleavedLayout-extra] */ +Containers::ArrayView attributes = + data.attributeData(); + +/* Take just positions and normals and add a four-byte padding in between */ +Trade::MeshData layout = MeshTools::interleavedLayout( + Trade::MeshData{MeshPrimitive::Triangles, 0}, vertexCount, { + attributes[data.attributeId(Trade::MeshAttribute::Position)], + Trade::MeshAttributeData{4}, + attributes[data.attributeId(Trade::MeshAttribute::Normal)] + }); +/* [interleavedLayout-extra] */ +} -indices = MeshTools::duplicate(indices, MeshTools::removeDuplicates(positions)); -/* [removeDuplicates1] */ +{ +Trade::MeshData data{MeshPrimitive::Lines, 0}; +Containers::ArrayView extraAttributes; +UnsignedInt vertexCount{}; +Containers::Array indexData; +/* [interleavedLayout-indices] */ +Trade::MeshData layout = + MeshTools::interleavedLayout(data, vertexCount, extraAttributes); + +Trade::MeshIndexData indices; +Trade::MeshData indexed{data.primitive(), + std::move(indexData), indices, + layout.releaseVertexData(), layout.releaseAttributeData()}; +/* [interleavedLayout-indices] */ +} + +{ +/* [removeDuplicates] */ +Containers::ArrayView data; + +std::size_t size; +Containers::Array indices; +std::tie(indices, size) = MeshTools::removeDuplicatesInPlace( + Containers::arrayCast<2, char>(data)); +data = data.prefix(size); +/* [removeDuplicates] */ } +#ifdef MAGNUM_BUILD_DEPRECATED { -/* [removeDuplicates2] */ +CORRADE_IGNORE_DEPRECATED_PUSH +/* [removeDuplicates-multiple] */ std::vector positions; std::vector texCoords; @@ -105,8 +181,10 @@ std::vector indices = MeshTools::combineIndexedArrays( std::make_pair(std::cref(positionIndices), std::ref(positions)), std::make_pair(std::cref(texCoordIndices), std::ref(texCoords)) ); -/* [removeDuplicates2] */ +/* [removeDuplicates-multiple] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { /* [transformVectors] */ diff --git a/doc/snippets/MagnumPrimitives.cpp b/doc/snippets/MagnumPrimitives.cpp index fcac06d215..7b86926d8c 100644 --- a/doc/snippets/MagnumPrimitives.cpp +++ b/doc/snippets/MagnumPrimitives.cpp @@ -26,8 +26,7 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Gradient.h" #include "Magnum/Primitives/Line.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; diff --git a/doc/snippets/MagnumSceneGraph-gl.cpp b/doc/snippets/MagnumSceneGraph-gl.cpp index 2f1a429bb5..bf5f4a5914 100644 --- a/doc/snippets/MagnumSceneGraph-gl.cpp +++ b/doc/snippets/MagnumSceneGraph-gl.cpp @@ -41,7 +41,7 @@ #include "Magnum/SceneGraph/Scene.h" #include "Magnum/Shaders/Flat.h" #include "Magnum/Shaders/Phong.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; using namespace Magnum::Math::Literals; diff --git a/doc/snippets/MagnumShaders.cpp b/doc/snippets/MagnumShaders.cpp index b5e51e8a42..670c68721a 100644 --- a/doc/snippets/MagnumShaders.cpp +++ b/doc/snippets/MagnumShaders.cpp @@ -290,17 +290,11 @@ mesh.addVertexBuffer(vertexIndices, 0, Shaders::MeshVisualizer::VertexIndex{}); { /* [MeshVisualizer-usage-no-geom1] */ -std::vector indices{ - // ... -}; -std::vector indexedPositions{ - // ... -}; +Containers::StridedArrayView1D indices; +Containers::StridedArrayView1D indexedPositions; /* De-indexing the position array */ -GL::Buffer vertices; -vertices.setData(MeshTools::duplicate(indices, indexedPositions), - GL::BufferUsage::StaticDraw); +GL::Buffer vertices{MeshTools::duplicate(indices, indexedPositions)}; GL::Mesh mesh; mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer::Position{}); diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 23c4caa94c..4383a3fead 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -30,19 +30,33 @@ #include "Magnum/FileCallback.h" #include "Magnum/ImageView.h" +#include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" +#include "Magnum/MeshTools/Interleave.h" #include "Magnum/Animation/Player.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/PhongMaterialData.h" #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/MeshTools/Compile.h" +#include "Magnum/Shaders/Phong.h" +#endif +#ifdef MAGNUM_TARGET_VK +#include "Magnum/Vk/Vulkan.h" +#endif + +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" #endif using namespace Magnum; @@ -169,6 +183,23 @@ Containers::Array animationData = data->release(); /* Take ownership */ /* [AnimationData-usage] */ } +{ +Trade::AnimationData data{nullptr, {}}; +/* [AnimationData-usage-mutable] */ +for(UnsignedInt i = 0; i != data.trackCount(); ++i) { + if(data.trackTargetType(i) != Trade::AnimationTrackTargetType::Translation3D) + continue; + /* Check prerequisites */ + if(!(data.dataFlags() & Trade::DataFlag::Mutable) || + data.trackType(i) != Trade::AnimationTrackType::Vector2) + Fatal{} << "Oops"; + + MeshTools::transformVectorsInPlace(Matrix4::scaling(Vector3::yScale(-1.0f)), + data.mutableTrack(i).values()); +} +/* [AnimationData-usage-mutable] */ +} + { /* [ImageData-construction] */ Containers::Array data; @@ -202,6 +233,245 @@ else #endif { +Trade::ImageData2D data{PixelFormat::RGB8Unorm, {}, nullptr}; +/* [ImageData-usage-mutable] */ +if(data.isCompressed() || + data.format() != PixelFormat::RGB8Unorm || + !(data.dataFlags() & Trade::DataFlag::Mutable)) + Fatal{} << ":("; + +for(auto&& row: data.mutablePixels()) + for(Color3ub& pixel: row) + pixel = Math::gather<'b', 'g', 'r'>(pixel); +/* [ImageData-usage-mutable] */ +} + +{ +/* [MeshIndexData-usage] */ +Containers::ArrayView indices; + +Trade::MeshIndexData data{indices}; +/* [MeshIndexData-usage] */ +} + +{ +/* [MeshAttributeData-usage] */ +Containers::StridedArrayView1D positions; + +Trade::MeshAttributeData data{Trade::MeshAttribute::Position, positions}; +/* [MeshAttributeData-usage] */ +} + +{ +/* [MeshAttributeData-usage-offset-only] */ +struct Vertex { + Vector3 position; + Vector4 color; +}; + +/* Layout defined statically, 15 vertices in total */ +constexpr Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + VertexFormat::Vector3, offsetof(Vertex, position), 15, sizeof(Vertex)}; +constexpr Trade::MeshAttributeData colors{Trade::MeshAttribute::Color, + VertexFormat::Vector4, offsetof(Vertex, color), 15, sizeof(Vertex)}; + +/* Actual data populated later */ +Containers::Array vertexData{15*sizeof(Vertex)}; +// ... +Trade::MeshData{MeshPrimitive::Triangles, std::move(vertexData), + {positions, colors}}; +/* [MeshAttributeData-usage-offset-only] */ +} + +#ifdef MAGNUM_TARGET_VK +{ +Containers::StridedArrayView1D data; +/* [MeshAttributeData-custom-vertex-format] */ +Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + vertexFormatWrap(VK_FORMAT_B10G11R11_UFLOAT_PACK32), + data}; +/* [MeshAttributeData-custom-vertex-format] */ +} +#endif + +#ifdef MAGNUM_TARGET_GL +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage] */ +/* Check that we have at least positions and normals */ +GL::Mesh mesh{data.primitive()}; +if(!data.hasAttribute(Trade::MeshAttribute::Position) || + !data.hasAttribute(Trade::MeshAttribute::Normal)) + Fatal{} << "Oh well"; + +/* Interleave vertex data */ +GL::Buffer vertices; +vertices.setData(MeshTools::interleave(data.positions3DAsArray(), + data.normalsAsArray())); +mesh.addVertexBuffer(std::move(vertices), 0, + Shaders::Phong::Position{}, Shaders::Phong::Normal{}); + +/* Set up an index buffer, if the mesh is indexed*/ +if(data.isIndexed()) { + GL::Buffer indices; + indices.setData(data.indicesAsArray()); + mesh.setIndexBuffer(std::move(indices), 0, MeshIndexType::UnsignedInt) + .setCount(data.indexCount()); +} else mesh.setCount(data.vertexCount()); +/* [MeshData-usage] */ +} + +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +GL::Mesh mesh{data.primitive()}; +/* [MeshData-usage-advanced] */ +/* Upload the original packed vertex data */ +GL::Buffer vertices; +vertices.setData(data.vertexData()); + +/* Set up the position and normal attributes */ +mesh.addVertexBuffer(vertices, + data.attributeOffset(Trade::MeshAttribute::Position), + data.attributeStride(Trade::MeshAttribute::Position), + GL::DynamicAttribute{Shaders::Phong::Position{}, + data.attributeFormat(Trade::MeshAttribute::Position)}); +mesh.addVertexBuffer(vertices, + data.attributeOffset(Trade::MeshAttribute::Normal), + data.attributeStride(Trade::MeshAttribute::Normal), + GL::DynamicAttribute{Shaders::Phong::Normal{}, + data.attributeFormat(Trade::MeshAttribute::Normal)}); + +// Set up other attributes ... + +/* Upload the original packed index data */ +if(data.isIndexed()) { + GL::Buffer indices; + indices.setData(data.indexData()); + mesh.setIndexBuffer(std::move(indices), 0, data.indexType()) + .setCount(data.indexCount()); +} else mesh.setCount(data.vertexCount()); +/* [MeshData-usage-advanced] */ +} + +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage-compile] */ +GL::Mesh mesh = MeshTools::compile(data); +/* [MeshData-usage-compile] */ +} +#endif + +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage-mutable] */ +/* Check prerequisites */ +if(!(data.vertexDataFlags() & Trade::DataFlag::Mutable) || + !data.hasAttribute(Trade::MeshAttribute::Position) || + data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3) + Fatal{} << "Oh well"; + +/* Scale the mesh two times */ +MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), + data.mutableAttribute(Trade::MeshAttribute::Position)); +/* [MeshData-usage-mutable] */ +} + +{ +std::size_t vertexCount{}, indexCount{}; +/* [MeshData-populating] */ +struct Vertex { + Vector3 position; + Vector4 color; +}; + +Containers::Array indexData{indexCount*sizeof(UnsignedShort)}; +Containers::Array vertexData{vertexCount*sizeof(Vertex)}; +// … +auto vertices = Containers::arrayCast(vertexData); +auto indices = Containers::arrayCast(indexData); + +Trade::MeshData data{MeshPrimitive::Triangles, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertices, + &vertices[0].position, vertexCount, sizeof(Vertex)}}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{vertices, + &vertices[0].color, vertexCount, sizeof(Vertex)}} + }}; +/* [MeshData-populating] */ +} + +{ +struct Vertex { + Vector3 position; + Vector4 color; +}; +/* [MeshData-populating-non-owned] */ +const UnsignedShort indices[] { + 0, 1, 2, + 2, 1, 3, + 3, 4, 5, + 5, 4, 6 +}; +Vertex vertices[7]; + +Trade::MeshData data{MeshPrimitive::Triangles, + Trade::DataFlags{}, indices, Trade::MeshIndexData{indices}, + Trade::DataFlag::Mutable, vertices, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{ + Containers::arrayView(vertices), &vertices[0].position, + Containers::arraySize(vertices), sizeof(Vertex)}}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{ + Containers::arrayView(vertices), &vertices[0].color, + Containers::arraySize(vertices), sizeof(Vertex)}} + }}; +/* [MeshData-populating-non-owned] */ +} + +{ +/* [MeshData-populating-custom] */ +/* Each face can consist of 15 triangles at most, triangleCount says how many + indices in triangleIds are valid */ +struct Face { + UnsignedShort triangleIds[15]; + UnsignedByte triangleCount; +}; + +constexpr Trade::MeshAttribute TriangleIds = Trade::meshAttributeCustom(0x01); +constexpr Trade::MeshAttribute TriangleCount = Trade::meshAttributeCustom(0x02); + +Containers::Array vertexData; +auto faces = Containers::arrayCast(vertexData); + +Trade::MeshData data{MeshPrimitive::Faces, std::move(vertexData), { + Trade::MeshAttributeData{TriangleIds, + Containers::StridedArrayView2D{faces, + &faces[0].triangleIds[0], + {faces.size(), 15}, + {sizeof(Face), sizeof(UnsignedShort)}}}, + Trade::MeshAttributeData{TriangleCount, + Containers::StridedArrayView1D{faces, + &faces[0].triangleCount, faces.size(), sizeof(Face)}} +}}; +/* [MeshData-populating-custom] */ + +/* [MeshData-populating-custom-retrieve] */ +Containers::StridedArrayView2D triangleIds = + data.attribute(TriangleIds); +Containers::StridedArrayView1D triangleCounts = + data.attribute(TriangleCount); +/* [MeshData-populating-custom-retrieve] */ +static_cast(triangleIds); +static_cast(triangleCounts); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +{ +CORRADE_IGNORE_DEPRECATED_PUSH Trade::MeshData2D& foo(); Trade::MeshData2D& data = foo(); /* [MeshData2D-transform] */ @@ -211,7 +481,9 @@ Matrix3 transformation = Matrix3::rotation(45.0_degf); MeshTools::transformPointsInPlace(transformation, data.positions(0)); /* [MeshData2D-transform] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { Trade::ObjectData2D& baz(); @@ -224,7 +496,9 @@ Matrix3 transformation = static_cast(transformation); } +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH Trade::MeshData3D& bar(); Trade::MeshData3D& data = bar(); /* [MeshData3D-transform] */ @@ -234,7 +508,9 @@ Matrix4 transformation = MeshTools::transformPointsInPlace(transformation, data.positions(0)); MeshTools::transformVectorsInPlace(transformation, data.normals(0)); /* [MeshData3D-transform] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { Trade::ObjectData3D& fizz(); diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index daef6d5249..09569afaf9 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -378,10 +378,9 @@ if(MAGNUM_TARGET_GL) set(_MAGNUM_DebugTools_GL_DEPENDENCY_IS_OPTIONAL ON) endif() -set(_MAGNUM_MeshTools_DEPENDENCIES ) +set(_MAGNUM_MeshTools_DEPENDENCIES Trade) if(MAGNUM_TARGET_GL) - # Trade is used only in compile(), which needs GL as well - list(APPEND _MAGNUM_MeshTools_DEPENDENCIES Trade GL) + list(APPEND _MAGNUM_MeshTools_DEPENDENCIES GL) endif() set(_MAGNUM_OpenGLTester_DEPENDENCIES GL) diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index 37fc5c8284..a1253ba6cc 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -39,6 +39,7 @@ set(Magnum_GracefulAssert_SRCS ImageView.cpp Mesh.cpp PixelFormat.cpp + VertexFormat.cpp Animation/Player.cpp Animation/Interpolation.cpp) @@ -60,6 +61,7 @@ set(Magnum_HEADERS Tags.h Timeline.h Types.h + VertexFormat.h visibility.h) set(Magnum_PRIVATE_HEADERS @@ -68,7 +70,8 @@ set(Magnum_PRIVATE_HEADERS Implementation/meshIndexTypeMapping.hpp Implementation/meshPrimitiveMapping.hpp Implementation/compressedPixelFormatMapping.hpp - Implementation/pixelFormatMapping.hpp) + Implementation/pixelFormatMapping.hpp + Implementation/vertexFormatMapping.hpp) # Functionality specific to static Windows builds if(CORRADE_TARGET_WINDOWS AND NOT CORRADE_TARGET_WINDOWS_RT AND MAGNUM_BUILD_STATIC) diff --git a/src/Magnum/DebugTools/ObjectRenderer.cpp b/src/Magnum/DebugTools/ObjectRenderer.cpp index 018ff20556..276ffd2680 100644 --- a/src/Magnum/DebugTools/ObjectRenderer.cpp +++ b/src/Magnum/DebugTools/ObjectRenderer.cpp @@ -31,8 +31,7 @@ #include "Magnum/Primitives/Axis.h" #include "Magnum/SceneGraph/Camera.h" #include "Magnum/Shaders/VertexColor.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace DebugTools { @@ -43,13 +42,13 @@ template struct Renderer; template<> struct Renderer<2> { static ResourceKey shader() { return {"VertexColorShader2D"}; } static ResourceKey mesh() { return {"object2d"}; } - static Trade::MeshData2D meshData() { return Primitives::axis2D(); } + static Trade::MeshData meshData() { return Primitives::axis2D(); } }; template<> struct Renderer<3> { static ResourceKey shader() { return {"VertexColorShader3D"}; } static ResourceKey mesh() { return {"object3d"}; } - static Trade::MeshData3D meshData() { return Primitives::axis3D(); } + static Trade::MeshData meshData() { return Primitives::axis3D(); } }; } diff --git a/src/Magnum/FileCallback.h b/src/Magnum/FileCallback.h index 3e7665a2c0..f676e79df4 100644 --- a/src/Magnum/FileCallback.h +++ b/src/Magnum/FileCallback.h @@ -67,7 +67,7 @@ enum class InputFileCallbackPolicy: UnsignedByte { * function is called or another file is opened. * * This can be the case for example when importing mesh data using - * @ref Trade::AbstractImporter::mesh3D() --- all vertex data might be + * @ref Trade::AbstractImporter::mesh() --- all vertex data might be * combined in a single binary file and each mesh occupies only a portion * of it. Note, however, that this might not be the case for all importers * --- see documentation of a particular plugin for concrete info. diff --git a/src/Magnum/GL/Attribute.cpp b/src/Magnum/GL/Attribute.cpp index 8fc5b39b29..66e028d091 100644 --- a/src/Magnum/GL/Attribute.cpp +++ b/src/Magnum/GL/Attribute.cpp @@ -28,6 +28,8 @@ #include #include +#include "Magnum/VertexFormat.h" + namespace Magnum { namespace GL { Debug& operator<<(Debug& debug, const DynamicAttribute::Kind value) { @@ -467,4 +469,95 @@ Debug& operator<<(Debug& debug, const Attribute>::DataTyp } +bool hasVertexFormat(const VertexFormat format) { + switch(vertexFormatComponentFormat(format)) { + case VertexFormat::UnsignedByte: + case VertexFormat::Byte: + case VertexFormat::UnsignedShort: + case VertexFormat::Short: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + case VertexFormat::Float: + return true; + + case VertexFormat::Half: + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + return true; + #else + return false; + #endif + + case VertexFormat::Double: + #ifndef MAGNUM_TARGET_GLES + return true; + #else + return false; + #endif + + /* Nothing else expected to be returned from + vertexFormatComponentFormat() */ + default: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } +} + +DynamicAttribute::DynamicAttribute(const Kind kind, UnsignedInt location, const VertexFormat format, GLint maxComponents): _kind{kind}, _location{location}, _components{Components(vertexFormatComponentCount(format))} { + CORRADE_ASSERT(hasVertexFormat(format), + "GL::DynamicAttribute:" << format << "isn't available on this target", ); + + /* Translate component type to a GL-specific value */ + switch(vertexFormatComponentFormat(format)) { + #define _c(format) \ + case VertexFormat::format: \ + _dataType = DataType::format; \ + break; + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(Float) + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + _c(Half) + #endif + #ifndef MAGNUM_TARGET_GLES + _c(Double) + #endif + #undef _c + + /* Nothing else expected to be returned from + vertexFormatComponentFormat(), the unavailable formats were caught + by the hasVertexFormat() above already */ + default: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* If the type is normalized, switch the type to GenericNormalized (if not + already), and check that the attribute isn't expected to be integral or + long */ + if(isVertexFormatNormalized(format)) { + CORRADE_ASSERT(kind == Kind::Generic || kind == Kind::GenericNormalized, + "GL::DynamicAttribute: can't use" << format << "for a" << kind << "attribute", ); + _kind = Kind::GenericNormalized; + /* Otherwise check that non-normalized types aren't used for attributes + that are expected to be normalized. Float is an exception. */ + } else if(_dataType != DataType::Float) { + CORRADE_ASSERT(kind != Kind::GenericNormalized, + "GL::DynamicAttribute: can't use" << format << "for a normalized attribute", ); + /* Finally, float data types can't be used for integer attributes */ + } else { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(kind != Kind::Integral, + "GL::DynamicAttribute: can't use" << format << "for an integral attribute", ); + #endif + } + + #ifndef CORRADE_NO_DEBUG + /* Should pass also if maxComponents is GL_BGRA */ + CORRADE_ASSERT(GLint(_components) <= maxComponents, + "GL::DynamicAttribute: can't use" << format << "for a" << maxComponents << Debug::nospace << "-component attribute", ); + #else + static_cast(maxComponents); + #endif +} + }} diff --git a/src/Magnum/GL/Attribute.h b/src/Magnum/GL/Attribute.h index ae497691da..dc1a714306 100644 --- a/src/Magnum/GL/Attribute.h +++ b/src/Magnum/GL/Attribute.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::GL::Attribute + * @brief Class @ref Magnum::GL::Attribute, @ref Magnum::GL::DynamicAttribute, function @ref Magnum::GL::hasVertexFormat() */ #include @@ -346,7 +346,7 @@ location and base type. Note that unlike the compile-time specification, this class doesn't do any sanity verification and leaves most of the responsibility on the user. */ -class DynamicAttribute { +class MAGNUM_GL_EXPORT DynamicAttribute { public: /** * @brief Attribute kind @@ -543,6 +543,32 @@ class DynamicAttribute { */ template constexpr /*implicit*/ DynamicAttribute(const Attribute& attribute); + /** + * @brief Construct from a generic mesh attribute type + * @m_since_latest + * + * The @p type is expected to be available on given target and be + * compatible with @p kind --- i.e., normalized or floating-point for + * @ref Kind::GenericNormalized, non-normalized for @ref Kind::Integral + * / @ref Kind::Long and integral for @ref Kind::Integral. + * @see @ref hasVertexFormat() + */ + explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format): DynamicAttribute{kind, location, format, 4} {} + + /** + * @brief Construct from a compile-time attribute with a generic mesh attribute type override + * @m_since_latest + * + * Extracts kind and location from passed @ref Attribute type and calls + * @ref DynamicAttribute(Kind, UnsignedInt, VertexFormat). Expects that + * @p type's component count is not larger than the component count + * defined in the @p Attribute type. Note that only the + * compile-time-defined properties of the @p Attribute type are used, + * the instance-specific data type, options and component count is + * ignored. + */ + template explicit DynamicAttribute(const Attribute&, VertexFormat format); + /** @brief Attribute kind */ constexpr Kind kind() const { return _kind; } @@ -556,6 +582,10 @@ class DynamicAttribute { constexpr DataType dataType() const { return _dataType; } private: + /* Used by the constructor taking Attribute, defined in cpp to avoid + a dependency on for the assertion */ + explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format, GLint maxComponents); + Kind _kind; UnsignedInt _location; Components _components; @@ -571,6 +601,29 @@ MAGNUM_GL_EXPORT Debug& operator<<(Debug& debug, DynamicAttribute::Components); /** @debugoperatorclassenum{DynamicAttribute,DynamicAttribute::DataType} */ MAGNUM_GL_EXPORT Debug& operator<<(Debug& debug, DynamicAttribute::DataType); +/** +@brief Check availability of a generic mesh attribute type +@m_since_latest + +Some OpenGL targets don't support all mesh attribute types (for example OpenGL +ES doesn't support double-precision types). Returns @cpp false @ce if current +target can't support such type, @cpp true @ce otherwise. The @p type value is +expected to be valid. + +Note that, unlike with pixel format mapping, there's no way to represent an +implementation-specific mesh attribute type using a single 32-bit value and +thus this function returns @cpp false @ce also for all formats for which +@ref isVertexFormatImplementationSpecific() is @cpp true @ce --- you need to do +such mapping by hand by creating a corresponding @ref DynamicAttribute. + +@note Support of some formats depends on presence of a particular OpenGL + extension. Such check is outside of the scope of this function and you are + expected to verify extension availability before using such type. + +@see @ref DynamicAttribute::DynamicAttribute(Kind, UnsignedInt, VertexFormat) +*/ +MAGNUM_GL_EXPORT bool hasVertexFormat(Magnum::VertexFormat format); + namespace Implementation { template constexpr DynamicAttribute::Kind kindFor(typename std::enable_if::ScalarType, Float>::value, typename GL::Attribute::DataOptions>::type options) { @@ -892,6 +945,8 @@ template struct Attribute>: Attribute constexpr DynamicAttribute::DynamicAttribute(const Attribute& attribute): _kind{Implementation::kindFor(attribute.dataOptions())}, _location{location_}, _components{Components(GLint(attribute.components()))}, _dataType{DataType(GLenum(attribute.dataType()))} {} +template DynamicAttribute::DynamicAttribute(const Attribute& attribute, const VertexFormat format): DynamicAttribute{Implementation::kindFor(attribute.dataOptions()), location_, format, GLint(Implementation::Attribute::DefaultComponents)} {} + }} #endif diff --git a/src/Magnum/GL/CMakeLists.txt b/src/Magnum/GL/CMakeLists.txt index 1e61c5cda1..fdaec99fc1 100644 --- a/src/Magnum/GL/CMakeLists.txt +++ b/src/Magnum/GL/CMakeLists.txt @@ -27,7 +27,6 @@ set(MagnumGL_SRCS AbstractObject.cpp AbstractQuery.cpp AbstractShaderProgram.cpp - Attribute.cpp Buffer.cpp Context.cpp DefaultFramebuffer.cpp @@ -56,6 +55,7 @@ set(MagnumGL_SRCS set(MagnumGL_GracefulAssert_SRCS AbstractFramebuffer.cpp AbstractTexture.cpp + Attribute.cpp CubeMapTexture.cpp Mesh.cpp MeshView.cpp diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index 438a21820f..d267784349 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -54,7 +54,10 @@ constexpr MeshPrimitive PrimitiveMapping[]{ MeshPrimitive::LineStrip, MeshPrimitive::Triangles, MeshPrimitive::TriangleStrip, - MeshPrimitive::TriangleFan + MeshPrimitive::TriangleFan, + MeshPrimitive(~UnsignedInt{}), /* Instances */ + MeshPrimitive(~UnsignedInt{}), /* Faces */ + MeshPrimitive(~UnsignedInt{}) /* Edges */ }; constexpr MeshIndexType IndexTypeMapping[]{ @@ -65,16 +68,31 @@ constexpr MeshIndexType IndexTypeMapping[]{ } +bool hasMeshPrimitive(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return true; + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveMapping), + "GL::hasPrimitive(): invalid primitive" << primitive, {}); + return UnsignedInt(PrimitiveMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; +} + MeshPrimitive meshPrimitive(const Magnum::MeshPrimitive primitive) { - CORRADE_ASSERT(UnsignedInt(primitive) < Containers::arraySize(PrimitiveMapping), + if(isMeshPrimitiveImplementationSpecific(primitive)) + return meshPrimitiveUnwrap(primitive); + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveMapping), "GL::meshPrimitive(): invalid primitive" << primitive, {}); - return PrimitiveMapping[UnsignedInt(primitive)]; + const MeshPrimitive out = PrimitiveMapping[UnsignedInt(primitive) - 1]; + CORRADE_ASSERT(out != MeshPrimitive(~UnsignedInt{}), + "GL::meshPrimitive(): unsupported primitive" << primitive, {}); + return out; } MeshIndexType meshIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) < Containers::arraySize(IndexTypeMapping), + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), "GL::meshIndexType(): invalid type" << type, {}); - return IndexTypeMapping[UnsignedInt(type)]; + return IndexTypeMapping[UnsignedInt(type) - 1]; } #ifndef DOXYGEN_GENERATING_OUTPUT diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index ba6aac1efa..9b953f0948 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -136,9 +136,30 @@ enum class MeshPrimitive: GLenum { #endif }; +/** +@brief Check availability of a generic mesh primitive +@m_since_latest + +Returns @cpp false @ce if OpenGL doesn't support such primitive, @cpp true @ce +otherwise. Moreover, returns @cpp true @ce also for all formats that are +@ref isMeshPrimitiveImplementationSpecific(). The @p primitive value is +expected to be valid. +@see @ref meshPrimitive() +*/ +MAGNUM_GL_EXPORT bool hasMeshPrimitive(Magnum::MeshPrimitive primitive); + /** @brief Convert generic mesh primitive to OpenGL mesh primitive +In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for +@p primitive, maps it to a corresponding OpenGL mesh primitive. In case +@ref isMeshPrimitiveImplementationSpecific() returns @cpp true @ce, assumes +@p primitive stores OpenGL-specific mesh primitive and returns +@ref meshPrimitiveUnwrap() cast to @ref GL::MeshPrimitive. + +Not all generic mesh primitives are available in OpenGL and this function +expects that given primitive is available. Use @ref hasMeshPrimitive() to +query availability of given primitive. @see @ref meshIndexType() */ MAGNUM_GL_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive); @@ -196,8 +217,8 @@ layout using @ref setIndexBuffer(). You can also use @ref MeshTools::compressInd to conveniently compress the indices based on the range used. There is also @ref MeshTools::compile() function which operates directly on -@ref Trade::MeshData2D / @ref Trade::MeshData3D and returns fully configured -mesh and vertex/index buffers for use with stock shaders. +@ref Trade::MeshData and returns fully configured mesh and vertex/index buffers +for use with stock shaders. @attention Note that, by default, neither vertex buffers nor index buffer is managed (e.g. deleted on destruction) by the mesh, so you have to manage diff --git a/src/Magnum/GL/Test/AttributeTest.cpp b/src/Magnum/GL/Test/AttributeTest.cpp index 3cc14a5659..8a88001047 100644 --- a/src/Magnum/GL/Test/AttributeTest.cpp +++ b/src/Magnum/GL/Test/AttributeTest.cpp @@ -27,6 +27,7 @@ #include #include +#include "Magnum/VertexFormat.h" #include "Magnum/GL/Attribute.h" namespace Magnum { namespace GL { namespace Test { namespace { @@ -53,6 +54,26 @@ struct AttributeTest: TestSuite::Tester { void attributeMatrixNxNd(); void attributeMatrixMxNd(); + void attributeFromGenericFormat(); + #ifndef MAGNUM_TARGET_GLES2 + void attributeFromGenericFormatIntegral(); + #endif + #ifndef MAGNUM_TARGET_GLES + void attributeFromGenericFormatLong(); + #endif + void attributeFromGenericFormatEnableNormalized(); + void attributeFromGenericFormatUnexpectedForNormalizedKind(); + #ifndef MAGNUM_TARGET_GLES2 + void attributeFromGenericFormatUnexpectedForIntegralKind(); + #endif + #ifndef MAGNUM_TARGET_GLES + void attributeFromGenericFormatUnexpectedForLongKind(); + #endif + void attributeFromGenericFormatTooManyComponents(); + void attributeFromGenericFormatNotAvailable(); + + void hasVertexFormat(); + void debugComponents1(); void debugComponents2(); void debugComponents3(); @@ -98,6 +119,26 @@ AttributeTest::AttributeTest() { &AttributeTest::attributeMatrixNxNd, &AttributeTest::attributeMatrixMxNd, + &AttributeTest::attributeFromGenericFormat, + #ifndef MAGNUM_TARGET_GLES2 + &AttributeTest::attributeFromGenericFormatIntegral, + #endif + #ifndef MAGNUM_TARGET_GLES + &AttributeTest::attributeFromGenericFormatLong, + #endif + &AttributeTest::attributeFromGenericFormatEnableNormalized, + &AttributeTest::attributeFromGenericFormatUnexpectedForNormalizedKind, + #ifndef MAGNUM_TARGET_GLES2 + &AttributeTest::attributeFromGenericFormatUnexpectedForIntegralKind, + #endif + #ifndef MAGNUM_TARGET_GLES + &AttributeTest::attributeFromGenericFormatUnexpectedForLongKind, + #endif + &AttributeTest::attributeFromGenericFormatTooManyComponents, + &AttributeTest::attributeFromGenericFormatNotAvailable, + + &AttributeTest::hasVertexFormat, + &AttributeTest::debugComponents1, &AttributeTest::debugComponents2, &AttributeTest::debugComponents3, @@ -491,6 +532,163 @@ void AttributeTest::attributeMatrixMxNd() { #endif } +void AttributeTest::attributeFromGenericFormat() { + DynamicAttribute a{DynamicAttribute::Kind::Generic, 3, + VertexFormat::UnsignedShort}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::UnsignedShort); + + /* Check that compile-time attribs work too */ + DynamicAttribute a2{Attribute<7, Vector3>{}, + VertexFormat::UnsignedShort}; + CORRADE_COMPARE(a2.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(a2.location(), 7); + CORRADE_COMPARE(a2.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(a2.dataType(), DynamicAttribute::DataType::UnsignedShort); + + DynamicAttribute b{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Vector2bNormalized}; + CORRADE_COMPARE(b.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(b.location(), 3); + CORRADE_COMPARE(b.components(), DynamicAttribute::Components::Two); + CORRADE_COMPARE(b.dataType(), DynamicAttribute::DataType::Byte); + + DynamicAttribute c{DynamicAttribute::Kind::Generic, 3, + VertexFormat::Vector4ui}; + CORRADE_COMPARE(c.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(c.location(), 3); + CORRADE_COMPARE(c.components(), DynamicAttribute::Components::Four); + CORRADE_COMPARE(c.dataType(), DynamicAttribute::DataType::UnsignedInt); + + /* This one shouldn't fail even though the normalization is (probably?) + ignored. Not exactly sure. */ + DynamicAttribute d{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Float}; + CORRADE_COMPARE(d.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(d.location(), 3); + CORRADE_COMPARE(d.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(d.dataType(), DynamicAttribute::DataType::Float); +} + +#ifndef MAGNUM_TARGET_GLES2 +void AttributeTest::attributeFromGenericFormatIntegral() { + DynamicAttribute a{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector3s}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Integral); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Three); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::Short); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void AttributeTest::attributeFromGenericFormatLong() { + DynamicAttribute a{DynamicAttribute::Kind::Long, 15, + VertexFormat::Vector2d}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Long); + CORRADE_COMPARE(a.location(), 15); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Two); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::Double); +} +#endif + +void AttributeTest::attributeFromGenericFormatEnableNormalized() { + DynamicAttribute a{DynamicAttribute::Kind::Generic, 3, + VertexFormat::Vector3ubNormalized}; + /* Generic is automatically switched to GenericNormalized */ + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Three); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::UnsignedByte); +} + +void AttributeTest::attributeFromGenericFormatUnexpectedForNormalizedKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Int}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Int for a normalized attribute\n"); +} + +#ifndef MAGNUM_TARGET_GLES2 +void AttributeTest::attributeFromGenericFormatUnexpectedForIntegralKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector2bNormalized}; + DynamicAttribute{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector3}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Vector2bNormalized for a GL::DynamicAttribute::Kind::Integral attribute\n" + "GL::DynamicAttribute: can't use VertexFormat::Vector3 for an integral attribute\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void AttributeTest::attributeFromGenericFormatUnexpectedForLongKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::Long, 3, + VertexFormat::UnsignedShortNormalized}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::UnsignedShortNormalized for a GL::DynamicAttribute::Kind::Long attribute\n"); +} +#endif + +void AttributeTest::attributeFromGenericFormatTooManyComponents() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{Attribute<7, Vector2>{}, VertexFormat::Vector3}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Vector3 for a 2-component attribute\n"); +} + +void AttributeTest::attributeFromGenericFormatNotAvailable() { + #ifndef MAGNUM_TARGET_GLES + CORRADE_SKIP("All attribute formats available on desktop GL."); + #else + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{Attribute<7, Vector2>{}, VertexFormat::Vector3d}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: VertexFormat::Vector3d isn't available on this target\n"); + #endif +} + +void AttributeTest::hasVertexFormat() { + CORRADE_VERIFY(GL::hasVertexFormat(Magnum::VertexFormat::Vector2i)); + #ifdef MAGNUM_TARGET_GLES + CORRADE_VERIFY(!GL::hasVertexFormat(Magnum::VertexFormat::Vector3d)); + #endif + + /* Ensure all generic formats are handled by going though all and executing + out functions on those. This goes through the first 16 bits, which + should be enough. Going through 32 bits takes 8 seconds, too much. */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto format = Magnum::VertexFormat(i); + /* Each case only verifies that hasVertexFormat() handles the format + and doesn't fall into unreachable code */ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(format) { + #define _c(format) \ + case Magnum::VertexFormat::format: \ + GL::hasVertexFormat(Magnum::VertexFormat::format); \ + break; + #include "Magnum/Implementation/vertexFormatMapping.hpp" + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } +} + void AttributeTest::debugComponents1() { typedef Attribute<3, Float> Attribute; diff --git a/src/Magnum/GL/Test/CMakeLists.txt b/src/Magnum/GL/Test/CMakeLists.txt index 2233b07117..1dface78d0 100644 --- a/src/Magnum/GL/Test/CMakeLists.txt +++ b/src/Magnum/GL/Test/CMakeLists.txt @@ -23,7 +23,7 @@ # DEALINGS IN THE SOFTWARE. # -corrade_add_test(GLAttributeTest AttributeTest.cpp LIBRARIES MagnumGL) +corrade_add_test(GLAttributeTest AttributeTest.cpp LIBRARIES MagnumGLTestLib) corrade_add_test(GLAbstractShaderProgramTest AbstractShaderProgramTest.cpp LIBRARIES MagnumGL) corrade_add_test(GLBufferTest BufferTest.cpp LIBRARIES MagnumGL) corrade_add_test(GLContextTest ContextTest.cpp LIBRARIES MagnumGL) diff --git a/src/Magnum/GL/Test/MeshTest.cpp b/src/Magnum/GL/Test/MeshTest.cpp index 880302abe6..9569b6fcbd 100644 --- a/src/Magnum/GL/Test/MeshTest.cpp +++ b/src/Magnum/GL/Test/MeshTest.cpp @@ -50,7 +50,10 @@ struct MeshTest: TestSuite::Tester { void drawViewCountNotSet(); void mapPrimitive(); + void mapPrimitiveImplementationSpecific(); + void mapPrimitiveUnsupported(); void mapPrimitiveInvalid(); + void mapIndexType(); void mapIndexTypeInvalid(); @@ -69,7 +72,10 @@ MeshTest::MeshTest() { &MeshTest::drawViewCountNotSet, &MeshTest::mapPrimitive, + &MeshTest::mapPrimitiveImplementationSpecific, + &MeshTest::mapPrimitiveUnsupported, &MeshTest::mapPrimitiveInvalid, + &MeshTest::mapIndexType, &MeshTest::mapIndexTypeInvalid, @@ -166,7 +172,8 @@ void MeshTest::mapPrimitive() { switch(primitive) { #define _c(primitive) \ case Magnum::MeshPrimitive::primitive: \ - CORRADE_VERIFY(UnsignedInt(meshPrimitive(Magnum::MeshPrimitive::primitive)) >= 0); \ + if(hasMeshPrimitive(Magnum::MeshPrimitive::primitive)) \ + CORRADE_VERIFY(UnsignedInt(meshPrimitive(Magnum::MeshPrimitive::primitive)) >= 0); \ break; #include "Magnum/Implementation/meshPrimitiveMapping.hpp" #undef _c @@ -177,13 +184,28 @@ void MeshTest::mapPrimitive() { } } +void MeshTest::mapPrimitiveImplementationSpecific() { + CORRADE_VERIFY(hasMeshPrimitive(meshPrimitiveWrap(GL_LINES))); + CORRADE_COMPARE(meshPrimitive(meshPrimitiveWrap(GL_LINES)), + MeshPrimitive::Lines); +} + +void MeshTest::mapPrimitiveUnsupported() { + std::ostringstream out; + Error redirectError{&out}; + meshPrimitive(Magnum::MeshPrimitive::Instances); + CORRADE_COMPARE(out.str(), "GL::meshPrimitive(): unsupported primitive MeshPrimitive::Instances\n"); +} + void MeshTest::mapPrimitiveInvalid() { std::ostringstream out; Error redirectError{&out}; - meshPrimitive(Magnum::MeshPrimitive(0x123)); + meshPrimitive(Magnum::MeshPrimitive{}); + meshPrimitive(Magnum::MeshPrimitive(0x12)); CORRADE_COMPARE(out.str(), - "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x123)\n"); + "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x0)\n" + "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x12)\n"); } void MeshTest::mapIndexType() { @@ -218,9 +240,11 @@ void MeshTest::mapIndexTypeInvalid() { std::ostringstream out; Error redirectError{&out}; - meshIndexType(Magnum::MeshIndexType(0x123)); + meshIndexType(Magnum::MeshIndexType(0x0)); + meshIndexType(Magnum::MeshIndexType(0x12)); CORRADE_COMPARE(out.str(), - "GL::meshIndexType(): invalid type MeshIndexType(0x123)\n"); + "GL::meshIndexType(): invalid type MeshIndexType(0x0)\n" + "GL::meshIndexType(): invalid type MeshIndexType(0x12)\n"); } void MeshTest::debugPrimitive() { diff --git a/src/Magnum/Image.cpp b/src/Magnum/Image.cpp index 30be47f01d..fed0d70df4 100644 --- a/src/Magnum/Image.cpp +++ b/src/Magnum/Image.cpp @@ -73,11 +73,11 @@ template std::pair Containers::StridedArrayView Image::pixels() { - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template Containers::StridedArrayView Image::pixels() const { - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template Containers::Array Image::release() { diff --git a/src/Magnum/ImageView.cpp b/src/Magnum/ImageView.cpp index 805bc7b28e..98ba949936 100644 --- a/src/Magnum/ImageView.cpp +++ b/src/Magnum/ImageView.cpp @@ -63,7 +63,7 @@ template void ImageView::setData template auto ImageView::pixels() const -> Containers::StridedArrayView { if(!_data && !_data.size()) return {}; - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{reinterpret_cast(data.data()), data.size()} {} diff --git a/src/Magnum/Implementation/ImageProperties.h b/src/Magnum/Implementation/ImageProperties.h index 0786d6f856..dd495254c7 100644 --- a/src/Magnum/Implementation/ImageProperties.h +++ b/src/Magnum/Implementation/ImageProperties.h @@ -45,7 +45,7 @@ template std::pair::pad(dataProperties.first), Math::Vector::pad(dataProperties.second)); } -template Containers::StridedArrayView imagePixelView(Image& image) { +template Containers::StridedArrayView imagePixelView(Image& image, const Data data) { const std::pair, VectorTypeFor> properties = image.dataProperties(); /* Size in the last dimension is byte size of the pixel, the remaining @@ -70,7 +70,7 @@ template Containers::StridedArrayV static_assert(sizeof(decltype(image.data().front())) == 1, "pointer arithmetic expects image data type to have 1 byte"); - return {image.data().suffix(properties.first[dimensions - 1]), image.data() + properties.first.sum(), size, stride}; + return {data.suffix(properties.first[dimensions - 1]), data + properties.first.sum(), size, stride}; } }} diff --git a/src/Magnum/Implementation/meshPrimitiveMapping.hpp b/src/Magnum/Implementation/meshPrimitiveMapping.hpp index 0f503c8a47..5275a0ead6 100644 --- a/src/Magnum/Implementation/meshPrimitiveMapping.hpp +++ b/src/Magnum/Implementation/meshPrimitiveMapping.hpp @@ -32,4 +32,7 @@ _c(LineStrip) _c(Triangles) _c(TriangleStrip) _c(TriangleFan) +_c(Instances) +_c(Faces) +_c(Edges) #endif diff --git a/src/Magnum/Implementation/vertexFormatMapping.hpp b/src/Magnum/Implementation/vertexFormatMapping.hpp new file mode 100644 index 0000000000..2b2dc2b453 --- /dev/null +++ b/src/Magnum/Implementation/vertexFormatMapping.hpp @@ -0,0 +1,80 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/* Each entry is just the name, for debug output and configuration to string */ +#ifdef _c +_c(Float) +_c(Half) +_c(Double) +_c(UnsignedByte) +_c(UnsignedByteNormalized) +_c(Byte) +_c(ByteNormalized) +_c(UnsignedShort) +_c(UnsignedShortNormalized) +_c(Short) +_c(ShortNormalized) +_c(UnsignedInt) +_c(Int) +_c(Vector2) +_c(Vector2h) +_c(Vector2d) +_c(Vector2ub) +_c(Vector2ubNormalized) +_c(Vector2b) +_c(Vector2bNormalized) +_c(Vector2us) +_c(Vector2usNormalized) +_c(Vector2s) +_c(Vector2sNormalized) +_c(Vector2ui) +_c(Vector2i) +_c(Vector3) +_c(Vector3h) +_c(Vector3d) +_c(Vector3ub) +_c(Vector3ubNormalized) +_c(Vector3b) +_c(Vector3bNormalized) +_c(Vector3us) +_c(Vector3usNormalized) +_c(Vector3s) +_c(Vector3sNormalized) +_c(Vector3ui) +_c(Vector3i) +_c(Vector4) +_c(Vector4h) +_c(Vector4d) +_c(Vector4ub) +_c(Vector4ubNormalized) +_c(Vector4b) +_c(Vector4bNormalized) +_c(Vector4us) +_c(Vector4usNormalized) +_c(Vector4s) +_c(Vector4sNormalized) +_c(Vector4ui) +_c(Vector4i) +#endif diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index f169d18841..8cfd87da3c 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -861,7 +861,8 @@ typedef BasicMutableCompressedImageView<2> MutableCompressedImageView2D; typedef BasicMutableCompressedImageView<3> MutableCompressedImageView3D; enum class MeshPrimitive: UnsignedInt; -enum class MeshIndexType: UnsignedInt; +enum class MeshIndexType: UnsignedByte; +enum class VertexFormat: UnsignedInt; enum class PixelFormat: UnsignedInt; enum class CompressedPixelFormat: UnsignedInt; diff --git a/src/Magnum/Mesh.cpp b/src/Magnum/Mesh.cpp index 97e8ad83dc..6ba49fed2d 100644 --- a/src/Magnum/Mesh.cpp +++ b/src/Magnum/Mesh.cpp @@ -56,8 +56,12 @@ constexpr const char* MeshPrimitiveNames[] { Debug& operator<<(Debug& debug, const MeshPrimitive value) { debug << "MeshPrimitive" << Debug::nospace; - if(UnsignedInt(value) < Containers::arraySize(MeshPrimitiveNames)) { - return debug << "::" << Debug::nospace << MeshPrimitiveNames[UnsignedInt(value)]; + if(isMeshPrimitiveImplementationSpecific(value)) { + return debug << "::ImplementationSpecific(" << Debug::nospace << reinterpret_cast(meshPrimitiveUnwrap(value)) << Debug::nospace << ")"; + } + + if(UnsignedInt(value) - 1 < Containers::arraySize(MeshPrimitiveNames)) { + return debug << "::" << Debug::nospace << MeshPrimitiveNames[UnsignedInt(value) - 1]; } return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; @@ -76,8 +80,8 @@ constexpr const char* MeshIndexTypeNames[] { Debug& operator<<(Debug& debug, const MeshIndexType value) { debug << "MeshIndexType" << Debug::nospace; - if(UnsignedInt(value) < Containers::arraySize(MeshIndexTypeNames)) { - return debug << "::" << Debug::nospace << MeshIndexTypeNames[UnsignedInt(value)]; + if(UnsignedInt(value) - 1 < Containers::arraySize(MeshIndexTypeNames)) { + return debug << "::" << Debug::nospace << MeshIndexTypeNames[UnsignedInt(value) - 1]; } return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; @@ -89,31 +93,31 @@ Debug& operator<<(Debug& debug, const MeshIndexType value) { namespace Corrade { namespace Utility { std::string ConfigurationValue::toString(Magnum::MeshPrimitive value, ConfigurationValueFlags) { - if(Magnum::UnsignedInt(value) < Containers::arraySize(Magnum::MeshPrimitiveNames)) - return Magnum::MeshPrimitiveNames[Magnum::UnsignedInt(value)]; + if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshPrimitiveNames)) + return Magnum::MeshPrimitiveNames[Magnum::UnsignedInt(value) - 1]; return {}; } Magnum::MeshPrimitive ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshPrimitiveNames); ++i) - if(stringValue == Magnum::MeshPrimitiveNames[i]) return Magnum::MeshPrimitive(i); + if(stringValue == Magnum::MeshPrimitiveNames[i]) return Magnum::MeshPrimitive(i + 1); - return Magnum::MeshPrimitive::Points; + return {}; } std::string ConfigurationValue::toString(Magnum::MeshIndexType value, ConfigurationValueFlags) { - if(Magnum::UnsignedInt(value) < Containers::arraySize(Magnum::MeshIndexTypeNames)) - return Magnum::MeshIndexTypeNames[Magnum::UnsignedInt(value)]; + if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshIndexTypeNames)) + return Magnum::MeshIndexTypeNames[Magnum::UnsignedInt(value) - 1]; return {}; } Magnum::MeshIndexType ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshIndexTypeNames); ++i) - if(stringValue == Magnum::MeshIndexTypeNames[i]) return Magnum::MeshIndexType(i); + if(stringValue == Magnum::MeshIndexTypeNames[i]) return Magnum::MeshIndexType(i + 1); - return Magnum::MeshIndexType::UnsignedInt; + return {}; } }} diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 4816b9728c..4e80165726 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -26,9 +26,10 @@ */ /** @file - * @brief Enum @ref Magnum::MeshPrimitive, @ref Magnum::MeshIndexType, function @ref Magnum::meshIndexTypeSize() + * @brief Enum @ref Magnum::MeshPrimitive, @ref Magnum::MeshIndexType, function @ref Magnum::isMeshPrimitiveImplementationSpecific(), @ref Magnum::meshPrimitiveWrap(), @ref Magnum::meshPrimitiveUnwrap(), @ref Magnum::meshIndexTypeSize() */ +#include #include #include "Magnum/Magnum.h" @@ -39,6 +40,11 @@ namespace Magnum { /** @brief Mesh primitive type +Can act also as a wrapper for implementation-specific mesh primitive types +using @ref meshPrimitiveWrap() and @ref meshPrimitiveUnwrap(). Distinction +between generic and implementation-specific primitive types can be done using +@ref isMeshPrimitiveImplementationSpecific(). + In case of OpenGL, corresponds to @ref GL::MeshPrimitive and is convertible to it using @ref GL::meshPrimitive(). See documentation of each value for more information about the mapping. @@ -53,6 +59,8 @@ for Metal, corresponds to @m_class{m-doc-external} [MTLPrimitiveType](https://de See documentation of each value for more information about the mapping. */ enum class MeshPrimitive: UnsignedInt { + /* Zero reserved for an invalid type (but not being a named value) */ + /** * Single points. * @@ -62,7 +70,7 @@ enum class MeshPrimitive: UnsignedInt { * or @m_class{m-doc-external} [MTLPrimitiveTypePoint](https://developer.apple.com/documentation/metal/mtlprimitivetype/mtlprimitivetypepoint?language=objc). * @m_keywords{D3D_PRIMITIVE_TOPOLOGY_POINTLIST MTLPrimitiveTypePoint} */ - Points, + Points = 1, /** * Each pair of vertices defines a single line, lines aren't @@ -127,12 +135,97 @@ enum class MeshPrimitive: UnsignedInt { * @def_vk_keyword{PRIMITIVE_TOPOLOGY_TRIANGLE_FAN,PrimitiveTopology}. Not * supported on D3D or Metal. */ - TriangleFan + TriangleFan, + + /** + * Per-instance data. + * @m_since_latest + * + * Has no direct mapping to GPU APIs, but can be used to annotate + * @ref Trade::MeshData containing per-instance data (such as colors, + * transformations or texture layers) and then used to populate an instance + * buffer. Index buffer has no defined meaning for instance data. + */ + Instances, + + /** + * Per-face data. + * @m_since_latest + * + * Can be used to annotate @ref Trade::MeshData containing data that are + * per-face, as opposed to per-vertex. Has no direct mapping to common GPU + * APIs, there it either has to be converted to per-vertex (which usually + * involves slightly duplicating the original per-vertex data) or accessed + * via a direct buffer/texture fetch from a shader using e.g. + * @glsl gl_VertexID @ce. Index buffer can be used to deduplicate per-face + * data. + */ + Faces, + + /** + * Per-edge data. + * @m_since_latest + * + * Can be used to annotate @ref Trade::MeshData containing data that are + * per-edge, as opposed to per-vertex. This is different from + * @ref MeshPrimitive::Lines as it has just one entry per line segment, + * instead of two. Has no direct mapping to common GPU APIs, there it has + * to be converted to per-vertex (which usually involves slightly + * duplicating the original per-vertex data). Index buffer can be used to + * deduplicate per-face data. Can also be used for example to describe a + * half-edge mesh representation. + * @see @ref Trade::meshAttributeCustom() + */ + Edges }; /** @debugoperatorenum{MeshPrimitive} */ MAGNUM_EXPORT Debug& operator<<(Debug& debug, MeshPrimitive value); +/** +@brief Whether a @ref MeshPrimitive value wraps an implementation-specific identifier +@m_since_latest + +Returns @cpp true @ce if value of @p primitive has its highest bit set, +@cpp false @ce otherwise. Use @ref meshPrimitiveWrap() and @ref meshPrimitiveUnwrap() +to wrap/unwrap an implementation-specific indentifier to/from +@ref MeshPrimitive. +*/ +constexpr bool isMeshPrimitiveImplementationSpecific(MeshPrimitive primitive) { + return UnsignedInt(primitive) & (1u << 31); +} + +/** +@brief Wrap an implementation-specific mesh primitive identifier in @ref MeshPrimitive +@m_since_latest + +Sets the highest bit on @p primitive to mark it as implementation-specific. +Expects that @p primitive fits into the remaining bits. Use +@ref meshPrimitiveUnwrap() for the inverse operation. +@see @ref isMeshPrimitiveImplementationSpecific() +*/ +template constexpr MeshPrimitive meshPrimitiveWrap(T implementationSpecific) { + static_assert(sizeof(T) <= 4, "types larger than 32bits are not supported"); + return CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(implementationSpecific) & (1u << 31)), + "meshPrimitiveWrap(): implementation-specific value" << reinterpret_cast(implementationSpecific) << "already wrapped or too large"), + MeshPrimitive((1u << 31)|UnsignedInt(implementationSpecific)); +} + +/** +@brief Unwrap an implementation-specific mesh primitive identifier from @ref MeshPrimitive +@m_since_latest + +Unsets the highest bit from @p primitive to extract the implementation-specific +value. Expects that @p primitive has it set. Use @ref meshPrimitiveWrap() for +the inverse operation. +@see @ref isMeshPrimitiveImplementationSpecific() +*/ +template constexpr T meshPrimitiveUnwrap(MeshPrimitive primitive) { + return CORRADE_CONSTEXPR_ASSERT(UnsignedInt(primitive) & (1u << 31), + "meshPrimitiveUnwrap():" << primitive << "isn't a wrapped implementation-specific value"), + T(UnsignedInt(primitive) & ~(1u << 31)); +} + /** @brief Mesh index type @@ -146,7 +239,9 @@ for more information about the mapping. Note that not every type is available there, use @ref Vk::hasVkIndexType() to check for its presence. @see @ref meshIndexTypeSize() */ -enum class MeshIndexType: UnsignedInt { +enum class MeshIndexType: UnsignedByte { + /* Zero reserved for an invalid type (but not being a named value) */ + /** * Unsigned byte * @@ -155,7 +250,7 @@ enum class MeshIndexType: UnsignedInt { * suggest (via debug output) using 16-byte types instead for better * efficiency. */ - UnsignedByte, + UnsignedByte = 1, /** * Unsigned short @@ -198,7 +293,7 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { /** * @brief Reads enum value as string * - * If the value is invalid, returns @ref Magnum::MeshPrimitive::Points "MeshPrimitive::Points". + * If the value is invalid, returns a zero (invalid) primitive. */ static Magnum::MeshPrimitive fromString(const std::string& stringValue, ConfigurationValueFlags); }; @@ -217,7 +312,7 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { /** * @brief Read enum value as string * - * If the value is invalid, returns @ref Magnum::MeshIndexType::UnsignedInt "MeshIndexType::UnsignedInt". + * If the value is invalid, returns a zero (invalid) type. */ static Magnum::MeshIndexType fromString(const std::string& stringValue, ConfigurationValueFlags); }; diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index 7c1c65d7dc..67cc0d2aef 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -29,14 +29,19 @@ set(MagnumMeshTools_SRCS # Files compiled with different flags for main library and unit test library set(MagnumMeshTools_GracefulAssert_SRCS - CombineIndexedArrays.cpp + Combine.cpp CompressIndices.cpp + Concatenate.cpp + Duplicate.cpp FlipNormals.cpp - GenerateNormals.cpp) + GenerateNormals.cpp + Interleave.cpp + RemoveDuplicates.cpp) set(MagnumMeshTools_HEADERS - CombineIndexedArrays.h + Combine.h CompressIndices.h + Concatenate.h Duplicate.h FlipNormals.h GenerateNormals.h @@ -52,14 +57,20 @@ set(MagnumMeshTools_INTERNAL_HEADERS Implementation/Tipsify.h) if(BUILD_DEPRECATED) - list(APPEND MagnumMeshTools_HEADERS GenerateFlatNormals.h) + list(APPEND MagnumMeshTools_GracefulAssert_SRCS + CombineIndexedArrays.cpp) + list(APPEND MagnumMeshTools_HEADERS + CombineIndexedArrays.h + GenerateFlatNormals.h) endif() if(TARGET_GL) list(APPEND MagnumMeshTools_SRCS - Compile.cpp FullScreenTriangle.cpp) + list(APPEND MagnumMeshTools_GracefulAssert_SRCS + Compile.cpp) + list(APPEND MagnumMeshTools_HEADERS Compile.h FullScreenTriangle.h) @@ -95,9 +106,9 @@ elseif(BUILD_STATIC_PIC) set_target_properties(MagnumMeshTools PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(MagnumMeshTools PUBLIC - Magnum) + Magnum MagnumTrade) if(TARGET_GL) - target_link_libraries(MagnumMeshTools PUBLIC MagnumGL MagnumTrade) + target_link_libraries(MagnumMeshTools PUBLIC MagnumGL) endif() install(TARGETS MagnumMeshTools @@ -120,9 +131,9 @@ if(BUILD_TESTS) set_target_properties(MagnumMeshToolsTestLib PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(MagnumMeshToolsTestLib PUBLIC - Magnum) + Magnum MagnumTrade) if(TARGET_GL) - target_link_libraries(MagnumMeshToolsTestLib PUBLIC MagnumGL MagnumTrade) + target_link_libraries(MagnumMeshToolsTestLib PUBLIC MagnumGL) endif() add_subdirectory(Test) diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp new file mode 100644 index 0000000000..749295c758 --- /dev/null +++ b/src/Magnum/MeshTools/Combine.cpp @@ -0,0 +1,211 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "Combine.h" + +#include +#include +#include +#include + +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/RemoveDuplicates.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { + +namespace { + +Trade::MeshData combineIndexedImplementation(const MeshPrimitive primitive, Containers::Array& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::ArrayView> data) { + /* Calculate attribute count and vertex stride */ + UnsignedInt attributeCount = 0; + UnsignedInt vertexStride = 0; + for(std::size_t i = 0; i != data.size(); ++i) { + attributeCount += data[i]->attributeCount(); + for(UnsignedInt j = 0; j != data[i]->attributeCount(); ++j) + vertexStride += vertexFormatSize(data[i]->attributeFormat(j)); + } + + /* Make the combined index array unique */ + Containers::Array indexData{indexCount*sizeof(UnsignedInt)}; + const auto indexDataI = Containers::arrayCast(indexData); + const UnsignedInt vertexCount = removeDuplicatesInPlaceInto( + Containers::StridedArrayView2D{combinedIndices, {indexCount, indexStride}}, + indexDataI); + + /* Allocate resulting attribute and vertex data and duplicate the + attributes there according to the combined index buffer */ + Containers::Array vertexData{Containers::NoInit, + vertexStride*vertexCount}; + Containers::Array attributeData{attributeCount}; + { + std::size_t indexOffset = 0; + std::size_t attributeOffset = 0; + std::size_t vertexOffset = 0; + for(const Trade::MeshData& mesh: data) { + const UnsignedInt indexSize = mesh.isIndexed() ? + meshIndexTypeSize(mesh.indexType()) : 4; + Containers::StridedArrayView2D indices{combinedIndices, + combinedIndices.data() + indexOffset, + {vertexCount, indexSize}, + {std::ptrdiff_t(indexStride), 1}}; + + for(UnsignedInt i = 0; i != mesh.attributeCount(); ++i) { + const UnsignedInt attributeSize = vertexFormatSize(mesh.attributeFormat(i)); + Containers::StridedArrayView2D dst{vertexData, + vertexData.data() + vertexOffset, + {vertexCount, attributeSize}, + {std::ptrdiff_t(vertexStride), 1}}; + duplicateInto(indices, mesh.attribute(i), dst); + vertexOffset += attributeSize; + attributeData[attributeOffset++] = Trade::MeshAttributeData{ + mesh.attributeName(i), mesh.attributeFormat(i), dst}; + } + + indexOffset += indexSize; + } + + /* Check we pre-calculated correctly */ + CORRADE_INTERNAL_ASSERT(attributeOffset == attributeCount && vertexOffset == vertexStride); + } + + return Trade::MeshData{primitive, + std::move(indexData), Trade::MeshIndexData{indexDataI}, + std::move(vertexData), std::move(attributeData), vertexCount}; +} + +} + +Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { + CORRADE_ASSERT(!data.empty(), + "MeshTools::combineIndexedAttributes(): no meshes passed", + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Decide on the output primitive and index count, calculated total + combined index type size */ + MeshPrimitive primitive; + UnsignedInt indexCount; + UnsignedInt indexStride = 0; + for(std::size_t i = 0; i != data.size(); ++i) { + CORRADE_ASSERT(data[i]->isIndexed(), + "MeshTools::combineIndexedAttributes(): data" << i << "is not indexed", + (Trade::MeshData{MeshPrimitive{}, 0})); + if(i == 0) { + primitive = data[i]->primitive(); + indexCount = data[i]->indexCount(); + } else { + CORRADE_ASSERT(data[i]->primitive() == primitive, + "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(data[i]->indexCount() == indexCount, + "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); + } + indexStride += meshIndexTypeSize(data[i]->indexType()); + } + + /** @todo handle alignment in the above somehow (duplicate() will fail when + reading 32-bit values from odd addresses on some platforms) */ + + /* Create a combined index array */ + Containers::Array combinedIndices{Containers::NoInit, + indexCount*indexStride}; + { + std::size_t indexOffset = 0; + for(const Trade::MeshData& mesh: data) { + const UnsignedInt indexSize = meshIndexTypeSize(mesh.indexType()); + Containers::StridedArrayView2D dst{combinedIndices, + combinedIndices.data() + indexOffset, + {indexCount, indexSize}, + {std::ptrdiff_t(indexStride), 1}}; + Utility::copy(mesh.indices(), dst); + indexOffset += indexSize; + } + + /* Check we pre-calculated correctly */ + CORRADE_INTERNAL_ASSERT(indexOffset == indexStride); + } + + return combineIndexedImplementation(primitive, combinedIndices, indexCount, indexStride, data); +} + +Trade::MeshData combineIndexedAttributes(std::initializer_list> data) { + return combineIndexedAttributes(Containers::arrayView(data)); +} + +Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes) { + CORRADE_ASSERT(mesh.isIndexed(), + "MeshTools::combineFaceAttributes(): vertex mesh is not indexed", + (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(mesh.primitive() == MeshPrimitive::Triangles && faceAttributes.primitive() == MeshPrimitive::Faces, + "MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got" << mesh.primitive() << "and" << faceAttributes.primitive(), + (Trade::MeshData{MeshPrimitive{}, 0})); + const UnsignedInt meshIndexCount = mesh.indexCount(); + const UnsignedInt faceIndexCount = faceAttributes.isIndexed() ? + faceAttributes.indexCount() : faceAttributes.vertexCount(); + CORRADE_ASSERT(faceIndexCount*3 == meshIndexCount, + "MeshTools::combineFaceAttributes(): expected" << meshIndexCount/3 << "face entries for" << meshIndexCount << "indices but got" << faceIndexCount, + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Make a combined index array. First copy the mesh indices as-is. */ + const UnsignedInt meshIndexSize = meshIndexTypeSize(mesh.indexType()); + const UnsignedInt faceIndexSize = faceAttributes.isIndexed() ? + meshIndexTypeSize(faceAttributes.indexType()) : 4; + const UnsignedInt indexStride = meshIndexSize + faceIndexSize; + Containers::Array combinedIndices{meshIndexCount*indexStride}; + Utility::copy(mesh.indices(), + Containers::StridedArrayView2D{combinedIndices, {meshIndexCount, meshIndexSize}, {std::ptrdiff_t(indexStride), 1}}); + + /* Then, if the face attributes are not indexed, remove duplicates and put + the resulting indices into the combined array above. For simplicity + assume face data are interleaved. */ + Containers::StridedArrayView3D combinedFaceIndices{combinedIndices, + combinedIndices.data() + meshIndexSize, + {3, faceIndexCount, faceIndexSize}, + {std::ptrdiff_t(indexStride), 3*std::ptrdiff_t(indexStride), 1}}; + if(!faceAttributes.isIndexed()) { + /** @todo this could go into a dedicated removeDuplicates(MeshData) + feature at some point, which would handle everything including + in-place / non-in-place, indexed / non-indexed etc. */ + CORRADE_ASSERT(isInterleaved(faceAttributes), + "MeshTools::combineFaceAttributes(): face attributes are not interleaved", + (Trade::MeshData{MeshPrimitive{}, 0})); + removeDuplicatesInto(interleavedData(faceAttributes), Containers::arrayCast<1, UnsignedInt>(combinedFaceIndices[0])); + + /* Otherwise, simply copy the indices directly */ + } else Utility::copy(faceAttributes.indices(), combinedFaceIndices[0]); + + /* Duplicate the vertex index to the other two vertices of each triangle */ + Utility::copy(combinedFaceIndices[0], combinedFaceIndices[1]); + Utility::copy(combinedFaceIndices[0], combinedFaceIndices[2]); + + /* Then combine the two into a single buffer */ + return combineIndexedImplementation(mesh.primitive(), combinedIndices, + meshIndexCount, indexStride, + Containers::arrayView>({ + mesh, faceAttributes + })); +} + +}} diff --git a/src/Magnum/MeshTools/Combine.h b/src/Magnum/MeshTools/Combine.h new file mode 100644 index 0000000000..45f68cf36b --- /dev/null +++ b/src/Magnum/MeshTools/Combine.h @@ -0,0 +1,113 @@ +#ifndef Magnum_MeshTools_Combine_h +#define Magnum_MeshTools_Combine_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Function @ref Magnum::MeshTools::combineIndexedAttributes(), @ref Magnum::MeshTools::combineFaceAttributes() + * @m_since_latest + */ + +#include + +#include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace MeshTools { + +/** +@brief Combine differently indexed attributes into a single mesh +@m_since_latest + +Assuming each @p data contains only unique vertex data, creates an indexed mesh +that contains all attributes from @p data combined, with duplicate vertices +removed. For example, when you have a position and a normal array, each indexed +with separate indices like this: + +@code{.cpp} +{pA, pB, pC, pD, pE, pF} // positions +{nA, nB, nC, nD, nE, nF, nG} // normals + +{0, 2, 5, 0, 0, 1, 3, 2, 2} // position indices +{1, 3, 4, 1, 4, 6, 1, 3, 1} // normal indices +@endcode + +Then the first triangle in the mesh is defined as +@cb{.cpp} {pA, nB}, {pC, nD}, {pF, nE} @ce. When combined together using this +function, the resulting mesh stays the same but there's just one index array, +indexing both positions and normals: + +@code{.cpp} +{{pA, nB}, {pC, nD}, {pF, nE}, {pA, nE}, {pB, nG}, {pD, nB}, {pC, nB}} + // unique pairs of positions and normals + +{0, 1, 2, 0, 3, 4, 5, 1, 6} // unified indices +@endcode + +The function preserves all vertex data including repeated or custom attributes. +The resulting mesh is interleaved, with all attributes packed tightly together. +If you need to add specific padding for alignment preservation, pass the result +to @ref interleave() and specify the paddings between attributes manually. +Similarly, for simplicity the resulting mesh has always +@ref MeshIndexType::UnsignedInt --- use @ref compressIndices(const Trade::MeshData&, MeshIndexType) +if you want to have it compressed to a smaller type. + +Expects that @p data is non-empty and all data have the same primitive and +index count. All inputs have to be indexed, although the particular +@ref MeshIndexType doesn't matter. For non-indexed attributes combining can be +done much more efficiently using @ref duplicate(const Trade::MeshData&, Containers::ArrayView), +alternatively you can turn a non-indexed attribute to an indexed one first +using @ref removeDuplicatesInPlace() and then call this function. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list> data); + +/** +@brief Combine per-face attributes into an existing mesh +@m_since_latest + +The resulting mesh will have all per-face attributes turned into per-vertex +attributes, leaving only unique combinations and adjusting the index buffer +accordingly. The resulting mesh has the same amount of indices, but likely +more vertices. + +Expects that @p mesh is indexed @ref MeshPrimitive::Triangles and +@p faceAttributes is indexed @ref MeshPrimitive::Faces, index count of the +latter corresponding to index count of the former. If @p faceAttributes is +indexed, it's assumed to have the data unique; if it's not indexed, it's first +made unique using @ref removeDuplicates() and in that case it's expected to +be interleaved. +@see @ref isInterleaved() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes); + +}} + +#endif diff --git a/src/Magnum/MeshTools/CombineIndexedArrays.cpp b/src/Magnum/MeshTools/CombineIndexedArrays.cpp index b023918167..09d8f367b9 100644 --- a/src/Magnum/MeshTools/CombineIndexedArrays.cpp +++ b/src/Magnum/MeshTools/CombineIndexedArrays.cpp @@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS + #include "CombineIndexedArrays.h" #include @@ -56,7 +58,9 @@ std::pair, std::vector> interleaveAndCombi /* Combine them */ std::vector combinedIndices; + CORRADE_IGNORE_DEPRECATED_PUSH std::tie(combinedIndices, interleavedArrays) = MeshTools::combineIndexArrays(interleavedArrays, stride); + CORRADE_IGNORE_DEPRECATED_POP return {combinedIndices, interleavedArrays}; } diff --git a/src/Magnum/MeshTools/CombineIndexedArrays.h b/src/Magnum/MeshTools/CombineIndexedArrays.h index daa8f122af..d946eea167 100644 --- a/src/Magnum/MeshTools/CombineIndexedArrays.h +++ b/src/Magnum/MeshTools/CombineIndexedArrays.h @@ -25,18 +25,30 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Function @ref Magnum::MeshTools::combineIndexArrays(), @ref Magnum::MeshTools::combineIndexedArrays() + * @m_deprecated_since_latest Use @ref Magnum/MeshTools/Combine.h and + * @ref Magnum::MeshTools::combineIndexedAttributes() instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include #include #include #include +#include #include "Magnum/Types.h" #include "Magnum/MeshTools/visibility.h" +#ifndef _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MeshData.h and combineIndexedAttributes() instead") +#endif + namespace Magnum { namespace MeshTools { /** @@ -44,6 +56,7 @@ namespace Magnum { namespace MeshTools { @param[in,out] arrays Index arrays to combine. These arrays are updated in-place to contain unique combinations of the original indices. @return Resulting combined index array +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. Creates new combined index array and updates the original ones with translation to new ones. For example, when you have position and normal array, each indexed @@ -81,13 +94,17 @@ This function calls @ref combineIndexArrays(const std::vector&, Uns internally. See also @ref combineIndexedArrays() which does the vertex data reordering automatically. */ -MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(const std::vector>>& arrays); +CORRADE_DEPRECATED("use combineIndexedAttributes() instead") MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(const std::vector>>& arrays); -/** @overload */ -MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(std::initializer_list>> arrays); +/** +@overload +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. +*/ +CORRADE_DEPRECATED("use combineIndexedAttributes() instead") MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(std::initializer_list>> arrays); /** @brief Combine interleaved index arrays +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. Unlike above, this function takes one interleaved array instead of separate index arrays. Continuing with the above example, you would call this function @@ -107,7 +124,7 @@ And second pair value is the cleaned up interleaved array: @see @ref combineIndexedArrays() */ -MAGNUM_MESHTOOLS_EXPORT std::pair, std::vector> combineIndexArrays(const std::vector& interleavedArrays, UnsignedInt stride); +CORRADE_DEPRECATED("use combineIndexedAttributes() instead") MAGNUM_MESHTOOLS_EXPORT std::pair, std::vector> combineIndexArrays(const std::vector& interleavedArrays, UnsignedInt stride); namespace Implementation { @@ -140,6 +157,7 @@ template inline void writeCombinedArrays(UnsignedInt stride @brief Combine indexed arrays @param[in,out] indexedArrays Index and attribute arrays @return Array with resulting indices +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. Creates new combined index array and reorders original attribute arrays so they can be indexed with the new single index array. @@ -160,7 +178,7 @@ procedure. /* Implementation note: It's done using tuples because it is more clear which parameter is index array and which is attribute array, mainly when both are of the same type. */ -template std::vector combineIndexedArrays(const std::pair&, std::vector&>&... indexedArrays) { +template CORRADE_DEPRECATED("use combineIndexedAttributes() instead") std::vector combineIndexedArrays(const std::pair&, std::vector&>&... indexedArrays) { /* Interleave and combine index arrays */ std::vector combinedIndices; std::vector interleavedCombinedIndexArrays; @@ -174,5 +192,8 @@ template std::vector combineIndexedArrays(const std::pa } }} +#else +#error use functions in Combine.h instead +#endif #endif diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index c4c5d5b804..7905e67305 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -25,19 +25,26 @@ #include "Compile.h" +#include #include -#include /** @todo remove once MeshData is sane */ #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" #include "Magnum/Math/Vector3.h" -#include "Magnum/Math/Color.h" -#include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/MeshTools/CompressIndices.h" +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#endif /* This header is included only privately and doesn't introduce any linker dependency, thus it's completely safe */ @@ -45,6 +52,170 @@ namespace Magnum { namespace MeshTools { +namespace { + +GL::Mesh compileInternal(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices, const CompileFlags flags) { + /* Only this one flag is allowed at this point */ + CORRADE_INTERNAL_ASSERT(!(flags & ~CompileFlag::NoWarnOnCustomAttributes)); + CORRADE_ASSERT((!meshData.isIndexed() || indices.id()) && vertices.id(), + "MeshTools::compile(): invalid external buffer(s)", GL::Mesh{}); + + /* Basics */ + GL::Mesh mesh; + mesh.setPrimitive(meshData.primitive()); + + /* Vertex data */ + GL::Buffer verticesRef = GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array); + for(UnsignedInt i = 0; i != meshData.attributeCount(); ++i) { + Containers::Optional attribute; + + /* Ignore implementation-specific formats because GL needs three + separate values to describe them so there's no way to put them in a + single 32-bit value :( */ + const VertexFormat format = meshData.attributeFormat(i); + if(isVertexFormatImplementationSpecific(format)) { + if(!(flags & CompileFlag::NoWarnOnCustomAttributes)) + Warning{} << "MeshTools::compile(): ignoring attribute" << meshData.attributeName(i) << "with an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)); + continue; + } + + switch(meshData.attributeName(i)) { + case Trade::MeshAttribute::Position: + /* Pick 3D position always, the type will properly reduce it to + a 2-component version if needed */ + attribute.emplace(Shaders::Generic3D::Position{}, format); + break; + case Trade::MeshAttribute::Normal: + attribute.emplace(Shaders::Generic3D::Normal{}, format); + break; + case Trade::MeshAttribute::TextureCoordinates: + /** @todo have Generic2D derived from Generic that has all + attribute definitions common for 2D and 3D */ + attribute.emplace(Shaders::Generic2D::TextureCoordinates{}, format); + break; + case Trade::MeshAttribute::Color: + /** @todo have Generic2D derived from Generic that has all + attribute definitions common for 2D and 3D */ + /* Pick Color4 always, the type will properly reduce it to a + 3-component version if needed */ + attribute.emplace(Shaders::Generic2D::Color4{}, format); + break; + + /* So it doesn't yell that we didn't handle a known attribute */ + case Trade::MeshAttribute::Custom: break; /* LCOV_EXCL_LINE */ + } + + if(!attribute) { + if(!(flags & CompileFlag::NoWarnOnCustomAttributes)) + Warning{} << "MeshTools::compile(): ignoring unknown attribute" << meshData.attributeName(i); + continue; + } + + /* For the first attribute move the buffer in, for all others use the + reference */ + if(vertices.id()) mesh.addVertexBuffer(std::move(vertices), + meshData.attributeOffset(i), meshData.attributeStride(i), + *attribute); + else mesh.addVertexBuffer(verticesRef, meshData.attributeOffset(i), + meshData.attributeStride(i), *attribute); + } + + if(meshData.isIndexed()) { + mesh.setIndexBuffer(std::move(indices), 0, meshData.indexType()) + .setCount(meshData.indexCount()); + } else mesh.setCount(meshData.vertexCount()); + + return mesh; +} + +GL::Mesh compileInternal(const Trade::MeshData& meshData, const CompileFlags flags) { + GL::Buffer indices{NoCreate}; + if(meshData.isIndexed()) { + indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; + indices.setData(meshData.indexData()); + } + + GL::Buffer vertices{GL::Buffer::TargetHint::Array}; + vertices.setData(meshData.vertexData()); + + return compileInternal(meshData, std::move(indices), std::move(vertices), flags); +} + +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices) { + return compileInternal(meshData, std::move(indices), std::move(vertices), {}); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices) { + return compileInternal(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array), CompileFlag::NoWarnOnCustomAttributes); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices) { + return compileInternal(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices), CompileFlag::NoWarnOnCustomAttributes); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices) { + return compileInternal(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array), CompileFlag::NoWarnOnCustomAttributes); +} + +GL::Mesh compile(const Trade::MeshData& meshData) { + return compileInternal(meshData, {}); +} + +GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { + /* If we want to generate normals, prepare a new mesh data and recurse, + with the flags unset */ + if(meshData.primitive() == MeshPrimitive::Triangles && (flags & (CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals))) { + CORRADE_ASSERT(meshData.attributeCount(Trade::MeshAttribute::Position), + "MeshTools::compile(): the mesh has no positions, can't generate normals", GL::Mesh{}); + /* This could fire if we have 2D positions or for packed formats */ + CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Position) == VertexFormat::Vector3, + "MeshTools::compile(): can't generate normals for" << meshData.attributeFormat(Trade::MeshAttribute::Position) << "positions", GL::Mesh{}); + + /* If the data already have a normal array, reuse its location, + otherwise mix in an extra one */ + Trade::MeshAttributeData normalAttribute; + Containers::ArrayView extra; + if(!meshData.hasAttribute(Trade::MeshAttribute::Normal)) { + normalAttribute = Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, VertexFormat::Vector3, + nullptr}; + extra = {&normalAttribute, 1}; + /* If we reuse a normal location, expect correct type */ + } else CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Normal) == VertexFormat::Vector3, + "MeshTools::compile(): can't generate normals into" << meshData.attributeFormat(Trade::MeshAttribute::Normal), GL::Mesh{}); + + /* If we want flat normals, we need to first duplicate everything using + the index buffer. Otherwise just interleave the potential extra + normal attribute in. */ + Trade::MeshData generated{MeshPrimitive::Points, 0}; + if(flags & CompileFlag::GenerateFlatNormals && meshData.isIndexed()) + generated = duplicate(meshData, extra); + else + generated = interleave(meshData, extra); + + /* Generate the normals. If we don't have the index buffer, we can only + generate flat ones. */ + if(flags & CompileFlag::GenerateFlatNormals || !meshData.isIndexed()) + generateFlatNormalsInto( + generated.attribute(Trade::MeshAttribute::Position), + generated.mutableAttribute(Trade::MeshAttribute::Normal)); + else + generateSmoothNormalsInto(generated.indices(), + generated.attribute(Trade::MeshAttribute::Position), + generated.mutableAttribute(Trade::MeshAttribute::Normal)); + + return compile(generated, flags & ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals)); + } + + flags &= ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals); + CORRADE_INTERNAL_ASSERT(!(flags & ~CompileFlag::NoWarnOnCustomAttributes)); + return compileInternal(meshData, flags); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH GL::Mesh compile(const Trade::MeshData2D& meshData) { GL::Mesh mesh; mesh.setPrimitive(meshData.primitive()); @@ -118,13 +289,11 @@ GL::Mesh compile(const Trade::MeshData2D& meshData) { return mesh; } -#ifdef MAGNUM_BUILD_DEPRECATED std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, GL::BufferUsage) { return std::make_tuple(compile(meshData), std::unique_ptr{new GL::Buffer{NoCreate}}, std::unique_ptr{meshData.isIndexed() ? new GL::Buffer{NoCreate} : nullptr}); } -#endif GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { GL::Mesh mesh; @@ -199,7 +368,7 @@ GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { normalStorage = generateFlatNormals(positions); useIndices = false; } else { - normalStorage = generateSmoothNormals(meshData.indices(), positions); + normalStorage = generateSmoothNormals(meshData.indices(), positions); useIndices = true; } @@ -280,12 +449,12 @@ GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { return mesh; } -#ifdef MAGNUM_BUILD_DEPRECATED std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, GL::BufferUsage) { return std::make_tuple(compile(meshData), std::unique_ptr{new GL::Buffer{NoCreate}}, std::unique_ptr{meshData.isIndexed() ? new GL::Buffer{NoCreate} : nullptr}); } +CORRADE_IGNORE_DEPRECATED_POP #endif }} diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index cd975994f0..8392c79279 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -51,7 +51,7 @@ namespace Magnum { namespace MeshTools { @brief Mesh compilation flag @m_since{2019,10} -@see @ref CompileFlags, @ref compile(const Trade::MeshData3D&, CompileFlags) +@see @ref CompileFlags, @ref compile(const Trade::MeshData&, CompileFlags) */ enum class CompileFlag: UnsignedByte { /** @@ -72,21 +72,135 @@ enum class CompileFlag: UnsignedByte { * is not a triangle mesh or doesn't have 3D positions, this flag does * nothing. If the mesh already has its own normals, these get replaced. */ - GenerateSmoothNormals = 1 << 1 + GenerateSmoothNormals = 1 << 1, + + /** + * By default, @ref compile() warns when it encounters custom attributes + * and attributes with implementation-specific format, as those get ignored + * by it. If you're binding those manually with + * @ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) or + * handling them in some other way on the application side already, use + * this flag to suppress the warning messages. + * @m_since_latest + */ + NoWarnOnCustomAttributes = 1 << 2 }; /** @brief Mesh compilation flags @m_since{2019,10} -@see @ref compile(const Trade::MeshData3D&, CompileFlags) +@see @ref compile(const Trade::MeshData&, CompileFlags) */ typedef Containers::EnumSet CompileFlags; CORRADE_ENUMSET_OPERATORS(CompileFlags) +/** +@brief Compile mesh data +@m_since_latest + +Configures a mesh for a @ref Shaders::Generic shader with a vertex buffer and +possibly also an index buffer, if the mesh is indexed. + +- If the mesh contains positions, these are bound to the + @ref Shaders::Generic2D::Position attribute if they are 2D or to + @ref Shaders::Generic3D::Position if they are 3D. +- If the mesh contains normals or if @ref CompileFlag::GenerateFlatNormals / + @ref CompileFlag::GenerateSmoothNormals is set, these are bound to + @ref Shaders::Generic3D::Normal. +- If the mesh contains texture coordinates, these are bound to + @ref Shaders::Generic::TextureCoordinates. +- If the mesh contains colors, these are bound to + @ref Shaders::Generic::Color3 / @ref Shaders::Generic::Color4 based on + their type. +- Custom attributes and known attributes of implementation-specific types + are ignored with a warning. See the @ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) + for an example showing how to bind them manually, and + @ref CompileFlag::NoWarnOnCustomAttributes to suppress the warning. + +If normal generation is not requested, @ref Trade::MeshData::indexData() and +@ref Trade::MeshData::vertexData() are uploaded as-is without any further +modifications, keeping the original layout and vertex formats. If +@ref CompileFlag::GenerateSmoothNormals is requested, vertex data is +interleaved together with the generated normals; if +@ref CompileFlag::GenerateFlatNormals is requested, the mesh is first +deindexed and then the vertex data is interleaved together with the generated +normals. + +The generated mesh owns the index and vertex buffers and there's no possibility +to access them afterwards. For alternative solutions see the +@ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) overloads. + +@note This function is available only if Magnum is compiled with + @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features + for more information. + +@see @ref shaders-generic +*/ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags); + +/** + * @overload + * @m_since_latest + */ +/* Separately because this one doesn't rely on duplicate() / interleave() / + generate*Normals() and thus the exe can be smaller when using this function + directly */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData); + +/** +@brief Compile mesh data using external buffers +@m_since_latest + +Assumes the whole vertex / index data are already uploaded to @p indices / +@p vertices and sets up the mesh using those. Can be used to have a single +index/vertex buffer when multiple @ref Trade::MeshData instances share the same +data arrays, or to allow buffer access later. For example: + +@snippet MagnumMeshTools-gl.cpp compile-external + +Another use case is specifying additional vertex attributes that are not +recognized by the function itself. You can choose among various r-value +overloads depending on whether you want to have the index/vertex buffers owned +by the mesh or not: + +@snippet MagnumMeshTools-gl.cpp compile-external-attributes + +If @p meshData is not indexed, the @p indices parameter is ignored --- in that +case you can pass a @ref NoCreate "NoCreate"-d instance to avoid allocating an +unnecessary OpenGL buffer object. + +Compared to @ref compile(const Trade::MeshData&, CompileFlags), this function +implicitly enables the @ref CompileFlag::NoWarnOnCustomAttributes flag, +assuming that custom attributes and attributes with implementation-specific +formats are explicitly handled on the application side. +*/ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices); + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compile 2D mesh data +@m_deprecated_since_latest Use @ref compile(const Trade::MeshData&, CompileFlags) + instead. Configures a mesh for @ref Shaders::Generic2D shader with vertex buffer and possibly also an index buffer, if the mesh is indexed. Positions are bound to @@ -109,19 +223,23 @@ greater flexibility. @see @ref shaders-generic */ -MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData2D& meshData); +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&) instead") MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData2D& meshData); +CORRADE_IGNORE_DEPRECATED_POP -#ifdef MAGNUM_BUILD_DEPRECATED -/** @brief @copybrief compile(const Trade::MeshData2D&) - * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData2D&) +/** @brief Compile 2D mesh data + * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData&) * instead. The @p usage parameter is ignored and returned buffer * instances are empty. */ -CORRADE_DEPRECATED("use compile(const Trade::MeshData2D&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, GL::BufferUsage usage); -#endif +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, GL::BufferUsage usage); +CORRADE_IGNORE_DEPRECATED_POP /** @brief Compile 3D mesh data +@m_deprecated_since_latest Use @ref compile(const Trade::MeshData&, CompileFlags) + instead. Configures mesh for @ref Shaders::Generic3D shader with vertex buffer and possibly also index buffer, if the mesh is indexed. Positions are bound to @@ -145,15 +263,18 @@ greater flexibility. @see @ref shaders-generic */ -MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags = {}); +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&, CompileFlags) instead") MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags = {}); +CORRADE_IGNORE_DEPRECATED_POP -#ifdef MAGNUM_BUILD_DEPRECATED -/** @brief @copybrief compile(const Trade::MeshData3D&, CompileFlags) - * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData3D&, CompileFlags) +/** @brief Compile 3D mesh data + * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData&, CompileFlags) * instead. The @p usage parameter is ignored and returned buffer * instances are empty. */ -CORRADE_DEPRECATED("use compile(const Trade::MeshData3D&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, GL::BufferUsage usage); +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, GL::BufferUsage usage); +CORRADE_IGNORE_DEPRECATED_POP #endif }} diff --git a/src/Magnum/MeshTools/CompressIndices.cpp b/src/Magnum/MeshTools/CompressIndices.cpp index c3b56d32ed..b37342a584 100644 --- a/src/Magnum/MeshTools/CompressIndices.cpp +++ b/src/Magnum/MeshTools/CompressIndices.cpp @@ -28,49 +28,154 @@ #include #include #include +#include #include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace { -template inline Containers::Array compress(const std::vector& indices) { +template inline Containers::Array compress(const Containers::StridedArrayView1D& indices, Long offset) { + /* Can't use Utility::copy() here because we're copying from a larger type + to a smaller one (and subtracting an offset in addition) */ Containers::Array buffer(indices.size()*sizeof(T)); for(std::size_t i = 0; i != indices.size(); ++i) { - T index = static_cast(indices[i]); + T index = static_cast(indices[i] - offset); std::memcpy(buffer.begin()+i*sizeof(T), &index, sizeof(T)); } return buffer; } +template std::pair, MeshIndexType> compressIndicesImplementation(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + const UnsignedInt max = Math::max(indices) - offset; + Containers::Array out; + MeshIndexType type; + const UnsignedInt log = Math::log(256, max); + + /* If it fits into 8 bytes and 8 bytes are allowed, pack into 8 */ + if(log == 0 && atLeast == MeshIndexType::UnsignedByte) { + out = compress(indices, offset); + type = MeshIndexType::UnsignedByte; + + /* Otherwise, if it fits into either 8 or 16 bytes and we allow either 8 or + 16, pack into 16 */ + } else if(log <= 1 && atLeast != MeshIndexType::UnsignedInt) { + out = compress(indices, offset); + type = MeshIndexType::UnsignedShort; + + /* Otherwise pack into 32 */ + } else { + out = compress(indices, offset); + type = MeshIndexType::UnsignedInt; + } + + return {std::move(out), type}; +} + +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + return compressIndicesImplementation(indices, atLeast, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + return compressIndicesImplementation(indices, atLeast, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + return compressIndicesImplementation(indices, atLeast, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const Long offset) { + return compressIndicesImplementation(indices, MeshIndexType::UnsignedShort, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const Long offset) { + return compressIndicesImplementation(indices, MeshIndexType::UnsignedShort, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const Long offset) { + return compressIndicesImplementation(indices, MeshIndexType::UnsignedShort, offset); } +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, const MeshIndexType atLeast, const Long offset) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::compressIndices(): second view dimension is not contiguous", {}); + if(indices.size()[1] == 4) + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), atLeast, offset); + else if(indices.size()[1] == 2) + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), atLeast, offset); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::compressIndices(): expected index type size 1, 2 or 4 but got" << indices.size()[1], {}); + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), atLeast, offset); + } +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, const Long offset) { + return compressIndices(indices, MeshIndexType::UnsignedShort, offset); +} + +Trade::MeshData compressIndices(Trade::MeshData&& data, MeshIndexType atLeast) { + CORRADE_ASSERT(data.isIndexed(), "MeshTools::compressIndices(): mesh data not indexed", (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Transfer vertex data as-is, as those don't need any changes. Release if + possible. */ + Containers::Array vertexData; + const UnsignedInt vertexCount = data.vertexCount(); + if(data.vertexDataFlags() & Trade::DataFlag::Owned) + vertexData = data.releaseVertexData(); + else { + vertexData = Containers::Array{Containers::NoInit, data.vertexData().size()}; + Utility::copy(data.vertexData(), vertexData); + } + + /* Compress the indices */ + UnsignedInt offset; + std::pair, MeshIndexType> result; + if(data.indexType() == MeshIndexType::UnsignedInt) { + auto indices = data.indices(); + offset = Math::min(indices); + result = compressIndicesImplementation(indices, atLeast, offset); + } else if(data.indexType() == MeshIndexType::UnsignedShort) { + auto indices = data.indices(); + offset = Math::min(indices); + result = compressIndicesImplementation(indices, atLeast, offset); + } else { + CORRADE_INTERNAL_ASSERT(data.indexType() == MeshIndexType::UnsignedByte); + auto indices = data.indices(); + offset = Math::min(indices); + result = compressIndicesImplementation(indices, atLeast, offset); + } + + /* Recreate the attribute array */ + const UnsignedInt newVertexCount = vertexCount - offset; + Containers::Array attributeData{data.attributeCount()}; + for(UnsignedInt i = 0, max = attributeData.size(); i != max; ++i) { + const UnsignedInt stride = data.attributeStride(i); + attributeData[i] = Trade::MeshAttributeData{data.attributeName(i), + data.attributeFormat(i), + Containers::StridedArrayView1D{vertexData, vertexData.data() + data.attributeOffset(i) + offset*stride, newVertexCount, stride}}; + } + + Trade::MeshIndexData indices{result.second, result.first}; + return Trade::MeshData{data.primitive(), std::move(result.first), indices, + std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData compressIndices(const Trade::MeshData& data, MeshIndexType atLeast) { + return compressIndices(Trade::MeshData{data.primitive(), + {}, data.indexData(), Trade::MeshIndexData{data.indices()}, + {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData())}, atLeast); +} + +#ifdef MAGNUM_BUILD_DEPRECATED std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices) { - /** @todo Performance hint when range can be represented by smaller value? */ const auto minmax = Math::minmax(indices); Containers::Array data; MeshIndexType type; - switch(Math::log(256, minmax.second)) { - case 0: - data = compress(indices); - type = MeshIndexType::UnsignedByte; - break; - case 1: - data = compress(indices); - type = MeshIndexType::UnsignedShort; - break; - case 2: - case 3: - data = compress(indices); - type = MeshIndexType::UnsignedInt; - break; - - default: - CORRADE_ASSERT(false, "MeshTools::compressIndices(): no type able to index" << minmax.second << "elements.", {}); /* LCOV_EXCL_LINE */ - } - + std::tie(data, type) = compressIndices(indices, MeshIndexType::UnsignedByte); return std::make_tuple(std::move(data), type, minmax.first, minmax.second); } @@ -90,5 +195,6 @@ template Containers::Array compressIndicesAs(const std::vector compressIndicesAs(const std::vector&); template Containers::Array compressIndicesAs(const std::vector&); template Containers::Array compressIndicesAs(const std::vector&); +#endif }} diff --git a/src/Magnum/MeshTools/CompressIndices.h b/src/Magnum/MeshTools/CompressIndices.h index eceb614bd2..0911a3ad0a 100644 --- a/src/Magnum/MeshTools/CompressIndices.h +++ b/src/Magnum/MeshTools/CompressIndices.h @@ -29,18 +29,146 @@ * @brief Function @ref Magnum::MeshTools::compressIndices() */ -#include -#include +#include +#include #include "Magnum/Mesh.h" #include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#endif namespace Magnum { namespace MeshTools { +/** +@brief Compress an index array +@param indices Index array +@param atLeast Smallest allowed type +@param offset Offset to subtract from each index +@return Compressed index array and corresponding type +@m_since_latest + +This function compresses @p indices to the smallest possible size. For example +when your indices have the maximum vertex index 463, it's wasteful to store +them in array of 32-bit integers, array of 16-bit integers is sufficient. The +@p atLeast parameter allows you to specify the smallest type to use and it +defaults to @ref MeshIndexType::UnsignedShort as 8-bit types are not friendly +to many GPUs (and for example unextended Vulkan or D3D12 don't even support +them). It's also possible to choose a type larger than the input type to +"inflate" an index buffer of a smaller type. Example usage: + +@snippet MagnumMeshTools-gl.cpp compressIndices + +In case the indices all start from a large offset, the @p offset parameter can +be used to subtract it, allowing them to be compressed even further. For +example, if all indices are in range @f$ [ 75000 ; 96000 ] @f$ (which fits only +into a 32-bit type), subtracting 75000 makes them in range @f$ [ 0; 21000 ] @f$ +which fits into 16 bits. Note that you also need to update vertex attribute +offsets accordingly. Example: + +@snippet MagnumMeshTools.cpp compressIndices-offset + +A negative @p offset value will do an operation inverse to the above. See also +@ref compressIndices(const Trade::MeshData&, MeshIndexType) that can do this +operation directly on a @ref Trade::MeshData instance. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); + +/** +@overload +@m_since_latest +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); + +/** +@overload +@m_since_latest +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, Long offset); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, Long offset); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, Long offset); + +/** +@brief Compress a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView2D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, Long offset); + +/** +@brief Compress mesh data indices +@m_since_latest + +Does the same as @ref compressIndices(const Containers::StridedArrayView2D&, MeshIndexType, Long), +but together with adjusting vertex attribute offsets in the passed +@ref Trade::MeshData instance. This function will unconditionally make a copy +of all vertex data, use @ref compressIndices(Trade::MeshData&&, MeshIndexType) +to avoid that copy. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData compressIndices(const Trade::MeshData& data, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + +/** +@brief Compress mesh data indices +@m_since_latest + +Compared to @ref compressIndices(const Trade::MeshData&, MeshIndexType) this +function can transfer ownership of @p data vertex buffer (in case it is +owned) to the returned instance instead of making a copy of it. Index and +attribute data are copied always. +@see @ref Trade::MeshData::vertexDataFlags() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData compressIndices(Trade::MeshData&& data, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compress vertex indices @param indices Index array @return Index range, type and compressed index array +@m_deprecated_since_latest Use @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) + instead. The index range isn't returned anymore, use @ref Math::minmax(const Containers::StridedArrayView1D&) + to get it if needed. This function takes index array and outputs them compressed to smallest possible size. For example when your indices have maximum number 463, it's @@ -49,14 +177,14 @@ sufficient. Example usage: -@snippet MagnumMeshTools-gl.cpp compressIndices - -@see @ref compressIndicesAs() +@snippet MagnumMeshTools-gl.cpp compressIndices-stl */ -std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> MAGNUM_MESHTOOLS_EXPORT compressIndices(const std::vector& indices); +CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices); /** @brief Compress vertex indices as given type +@m_deprecated_since_latest Use @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) + instead. The type can be either @ref Magnum::UnsignedByte "UnsignedByte", @ref Magnum::UnsignedShort "UnsignedShort" or @ref Magnum::UnsignedInt "UnsignedInt". @@ -65,16 +193,15 @@ Values in the index array are expected to be representable with given type. Example usage: @snippet MagnumMeshTools.cpp compressIndicesAs - -@see @ref compressIndices() */ -template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector& indices); +template CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) instead") MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector& indices); #if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector&); extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector&); extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector&); #endif +#endif }} diff --git a/src/Magnum/MeshTools/Concatenate.cpp b/src/Magnum/MeshTools/Concatenate.cpp new file mode 100644 index 0000000000..ea08e5ed96 --- /dev/null +++ b/src/Magnum/MeshTools/Concatenate.cpp @@ -0,0 +1,247 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "Concatenate.h" + +#include +#include +#include + +namespace Magnum { namespace MeshTools { + +namespace Implementation { + +std::pair concatenateIndexVertexCount(const Trade::MeshData& first, const Containers::ArrayView> next) { + UnsignedInt indexCount = first.isIndexed() ? first.indexCount() : 0; + UnsignedInt vertexCount = first.vertexCount(); + for(const Trade::MeshData& mesh: next) { + /* If the mesh is indexed, add to index count. If this is the first + indexed mesh, all previous meshes will have a trivial index buffer + generated for all their vertices */ + if(mesh.isIndexed()) { + if(!indexCount) indexCount = vertexCount; + indexCount += mesh.indexCount(); + + /* Otherwise, if some earlier mesh was indexed, this mesh will have a + trivial index buffer generated for all its vertices */ + } else if(indexCount) indexCount += mesh.vertexCount(); + + vertexCount += mesh.vertexCount(); + } + + return {indexCount, vertexCount}; +} + +/* std::hash for enumeration types is only since C++14, so we need to make our + own. It's amazing how extremely verbose this can get, ugh. */ +struct MeshAttributeHash: std::hash::type> { + std::size_t operator()(Trade::MeshAttribute value) const { + return std::hash::type>::operator()(static_cast::type>(value)); + } +}; + +Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Trade::MeshData& first, const Containers::ArrayView> next, const char* const assertPrefix, const std::size_t meshIndexOffset) { + #ifdef CORRADE_NO_ASSERT + static_cast(assertPrefix); + static_cast(meshIndexOffset); + #endif + + /* Convert the attributes from offset-only and zero vertex count to + absolute, referencing the vertex data array */ + for(Trade::MeshAttributeData& attribute: attributeData) { + attribute = Trade::MeshAttributeData{ + attribute.name(), attribute.format(), + Containers::StridedArrayView1D{vertexData, + vertexData + attribute.offset(vertexData), + vertexCount, attribute.stride()}}; + } + + /* Only list primitives are supported currently */ + /** @todo delegate to `indexTriangleStrip()` (`duplicate*()`?) etc when + those are done */ + CORRADE_ASSERT( + first.primitive() != MeshPrimitive::LineStrip && + first.primitive() != MeshPrimitive::LineLoop && + first.primitive() != MeshPrimitive::TriangleStrip && + first.primitive() != MeshPrimitive::TriangleFan, + assertPrefix << first.primitive() << "is not supported, turn it into a plain indexed mesh first", + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Populate the resulting instance with what we have. It'll be used below + for convenient access to vertex / index data */ + auto indices = Containers::arrayCast(indexData); + Trade::MeshData out{first.primitive(), + /* If the index array is empty, we're creating a non-indexed mesh (not + an indexed mesh with zero indices) */ + std::move(indexData), indices.empty() ? + Trade::MeshIndexData{} : Trade::MeshIndexData{indices}, + std::move(vertexData), std::move(attributeData), vertexCount}; + /* Create an attribute map. Yes, this is an inevitable fugly thing that + allocates like mad, while everything else is zero-alloc. + Containers::HashMap can't be here soon enough. */ + std::unordered_multimap, MeshAttributeHash> attributeMap; + attributeMap.reserve(out.attributeCount()); + for(UnsignedInt i = 0; i != out.attributeCount(); ++i) + attributeMap.emplace(out.attributeName(i), std::make_pair(i, false)); + + /* Go through all meshes and put all attributes and index arrays together. + The first mesh might get separately and thus can't be a part of the + view, so abuse the *defined* unsigned integer overflow to add it to the + loop. This probably breaks all coding guidelines on earth tho. */ + std::size_t indexOffset = 0; + std::size_t vertexOffset = 0; + for(std::size_t i = ~std::size_t{}; i != next.size(); ++i) { + const Trade::MeshData& mesh = i == ~std::size_t{} ? first : next[i].get(); + + /* This won't fire for i == ~std::size_t{}, as that's where + out.primitive() comes from */ + CORRADE_ASSERT(mesh.primitive() == out.primitive(), + assertPrefix << "expected" << out.primitive() << "but got" << mesh.primitive() << "in mesh" << i + meshIndexOffset, + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* If the mesh is indexed, copy the indices over, expanded to 32bit */ + if(mesh.isIndexed()) { + Containers::ArrayView dst = indices.slice(indexOffset, indexOffset + mesh.indexCount()); + mesh.indicesInto(dst); + indexOffset += mesh.indexCount(); + + /* Adjust indices for current vertex offset */ + for(UnsignedInt& index: dst) index += vertexOffset; + + /* Otherwise, if we need an index buffer (meaning at least one of the + meshes is indexed), generate a trivial index buffer */ + } else if(!indices.empty()) { + std::iota(indices + indexOffset, indices + indexOffset + mesh.vertexCount(), UnsignedInt(vertexOffset)); + indexOffset += mesh.vertexCount(); + } + + /* Reset markers saying which attribute has already been copied */ + for(auto it = attributeMap.begin(); it != attributeMap.end(); ++it) + it->second.second = false; + + /* Copy attributes to their destination, skipping ones that don't have + any equivalent in the destination mesh */ + for(UnsignedInt src = 0; src != mesh.attributeCount(); ++src) { + /* Go through destination attributes of the same name and find the + earliest one that hasn't been copied yet */ + auto range = attributeMap.equal_range(mesh.attributeName(src)); + UnsignedInt dst = ~UnsignedInt{}; + auto found = attributeMap.end(); + for(auto it = range.first; it != range.second; ++it) { + if(it->second.second) continue; + + /* The range is unordered so we need to go through everything + and pick one with smallest ID */ + if(it->second.first < dst) { + dst = it->second.first; + found = it; + } + } + + /* No corresponding attribute found, continue */ + if(dst == ~UnsignedInt{}) continue; + + /* Check format compatibility. This won't fire for i == + ~std::size_t{}, as that's where out.primitive() comes from */ + CORRADE_ASSERT(out.attributeFormat(dst) == mesh.attributeFormat(src), + assertPrefix << "expected" << out.attributeFormat(dst) << "for attribute" << dst << "(" << Debug::nospace << out.attributeName(dst) << Debug::nospace << ") but got" << mesh.attributeFormat(src) << "in mesh" << i + meshIndexOffset << "attribute" << src, + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Copy the data to a slice of the output, mark the attribute as + copied */ + Utility::copy(mesh.attribute(src), out.mutableAttribute(dst) + .slice(vertexOffset, vertexOffset + mesh.vertexCount())); + found->second.second = true; + } + + /* Update vertex offset for the next mesh */ + vertexOffset += mesh.vertexCount(); + } + + return out; +} + +} + +Trade::MeshData concatenate(Trade::MeshData&& first, const Containers::ArrayView> next) { + /* If there's just a single non-empty mesh and its data is owned, pass it + through, as it passes the guarantee that the returned data is always + owned. If it's empty, it doesn't matter that we drag it through the rest + as there will be no heavy allocation / copy made (and that also makes + tests easier to write). */ + if(first.indexDataFlags() & Trade::DataFlag::Owned && + first.vertexDataFlags() & Trade::DataFlag::Owned && + first.attributeCount() && first.vertexCount() && next.empty()) + return std::move(first); + + /* Calculate final attribute stride and offsets. Make a non-owning copy of + the attribute data to avoid interleavedLayout() stealing the original + (we still need it to be able to reference the original data). If there's + no attributes in the original array, pass just vertex count --- + otherwise MeshData will assert on that to avoid it getting lost. */ + Containers::Array attributeData; + if(first.attributeCount()) + attributeData = Implementation::interleavedLayout(Trade::MeshData{first.primitive(), + {}, first.vertexData(), + Trade::meshAttributeDataNonOwningArray(first.attributeData())}, {}); + else attributeData = + Implementation::interleavedLayout(Trade::MeshData{first.primitive(), + first.vertexCount()}, {}); + + /* Calculate total index/vertex count and allocate the target memory. + Index data are allocated with NoInit as the whole array will be written, + however vertex data might have holes and thus it's zero-initialized. */ + const std::pair indexVertexCount = Implementation::concatenateIndexVertexCount(first, next); + Containers::Array indexData{Containers::NoInit, + indexVertexCount.first*sizeof(UnsignedInt)}; + Containers::Array vertexData{Containers::ValueInit, + attributeData.empty() ? 0 : (attributeData[0].stride()*indexVertexCount.second)}; + return Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), first, next, "MeshTools::concatenate():", 0); +} + +Trade::MeshData concatenate(Trade::MeshData&& first, std::initializer_list> next) { + return concatenate(std::move(first), Containers::arrayView(next)); +} + +Trade::MeshData concatenate(const Trade::MeshData& first, const Containers::ArrayView> next) { + Containers::ArrayView indexData; + Trade::MeshIndexData indices; + if(first.isIndexed()) { + indexData = first.indexData(); + indices = Trade::MeshIndexData{first.indices()}; + } + + return concatenate(Trade::MeshData{first.primitive(), + {}, indexData, indices, + {}, first.vertexData(), Trade::meshAttributeDataNonOwningArray(first.attributeData()), + first.vertexCount(), + }, next); +} + +Trade::MeshData concatenate(const Trade::MeshData& first, std::initializer_list> next) { + return concatenate(first, Containers::arrayView(next)); +} + +}} diff --git a/src/Magnum/MeshTools/Concatenate.h b/src/Magnum/MeshTools/Concatenate.h new file mode 100644 index 0000000000..9e78af4a88 --- /dev/null +++ b/src/Magnum/MeshTools/Concatenate.h @@ -0,0 +1,153 @@ +#ifndef Magnum_MeshTools_Concatenate_h +#define Magnum_MeshTools_Concatenate_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Function @ref Magnum::MeshTools::concatenate(), @ref Magnum::MeshTools::concatenateInto() + * @m_since_latest + */ + +#include +#include + +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { + +namespace Implementation { + MAGNUM_MESHTOOLS_EXPORT std::pair concatenateIndexVertexCount(const Trade::MeshData& first, const Containers::ArrayView> next); + MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array&& indexData, UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Trade::MeshData& first, const Containers::ArrayView> next, const char* assertPrefix, std::size_t meshIndexOffset); +} + +/** +@brief Concatenate meshes together +@m_since_latest + +The returned mesh contains vertices from all meshes concatenated together. If +any mesh is indexed, the resulting mesh is indexed as well, with indices +adjusted for vertex offsets of particular meshes. The behavior is undefined if +any mesh has indices out of bounds for its particular vertex count. + +All attributes from the @p first mesh are taken; for each mesh in @p next, +attributes present in @p first are copied, superfluous attributes ignored and +missing attributes zeroed out. Matching attributes are expected to have the +same type, all meshes are expected to have the same primitive. The vertex data +are concatenated in the same order as passed, with no duplicate removal. +Returned instance vertex and index data flags always have both +@ref Trade::DataFlag::Owned and @ref Trade::DataFlag::Mutable to guarante +mutable access to particular parts of the concatenated mesh --- for example for +applying transformations. + +If an index buffer is needed, @ref MeshIndexType::UnsignedInt is always used. +Call @ref compressIndices(const Trade::MeshData&, MeshIndexType) on the result +to compress it to a smaller type, if desired. +@see @ref concatenate(Trade::MeshData&&, const Containers::ArrayView>), + @ref concatenateInto() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Trade::MeshData& first, const Containers::ArrayView> next = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Trade::MeshData& first, std::initializer_list> next); + +/** +@brief Concatenate meshes together +@m_since_latest + +Compared to @ref concatenate(const Trade::MeshData&, const Containers::ArrayView>), +if @p first has both vertex and index data owned and @p next is empty, it's +passed through without any extra allocations or other work. This can be used +for example to ensure a mesh is mutable in order to do various modifications on +its data: + +@snippet MagnumMeshTools.cpp concatenate-make-mutable +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Trade::MeshData&& first, const Containers::ArrayView> next = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Trade::MeshData&& first, std::initializer_list> next); + +/** +@brief Concatenate a list of meshes into a pre-existing destination, enlarging it if necessary +@tparam Allocator Allocator to use +@param[in,out] destination Destination mesh from which the output arrays as + well as desired attribute layout is taken +@param[in] meshes Meshes to concatenate +@m_since_latest + +Compared to @ref concatenate(const Trade::MeshData&, const Containers::ArrayView>) this +function resizes existing index and vertex buffers in @p destination using +@ref Containers::arrayResize() and given @p allocator, and reuses its +atttribute data array instead of always allocating new ones. Only the attribute +layout from @p destination is used, all vertex/index data are taken from +@p meshes. Expects that @p meshes contains at least one item. +*/ +template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, const Containers::ArrayView> meshes) { + CORRADE_ASSERT(!meshes.empty(), + "MeshTools::concatenateInto(): no meshes passed", ); + + std::pair indexVertexCount = Implementation::concatenateIndexVertexCount(meshes[0], meshes.suffix(1)); + + Containers::Array indexData; + if(indexVertexCount.first) { + indexData = destination.releaseIndexData(); + /* Everything is overwritten here so we don't need to zero-out the + memory */ + Containers::arrayResize(indexData, Containers::NoInit, indexVertexCount.first*sizeof(UnsignedInt)); + } + + Containers::Array attributeData = Implementation::interleavedLayout(std::move(destination), {}); + Containers::Array vertexData; + if(!attributeData.empty() && indexVertexCount.second) { + const UnsignedInt attributeStride = attributeData[0].stride(); + vertexData = destination.releaseVertexData(); + /* Resize to 0 and then to the desired size to zero-out whatever was + there, otherwise attributes that are not present in `meshes` would + be garbage */ + Containers::arrayResize(vertexData, 0); + Containers::arrayResize(vertexData, Containers::ValueInit, attributeStride*indexVertexCount.second); + } + + destination = Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), meshes[0], meshes.suffix(1), "MeshTools::concatenateInto():", 1); +} + +/** + * @overload + * @m_since_latest + */ +template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, const std::initializer_list> meshes) { + concatenateInto(destination, Containers::arrayView(meshes)); +} + +}} + +#endif diff --git a/src/Magnum/MeshTools/Duplicate.cpp b/src/Magnum/MeshTools/Duplicate.cpp new file mode 100644 index 0000000000..3d6b49ed6a --- /dev/null +++ b/src/Magnum/MeshTools/Duplicate.cpp @@ -0,0 +1,123 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "Duplicate.h" + +#include +#include + +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { + +namespace { + +template inline void duplicateIntoImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + CORRADE_ASSERT(out.size()[0] == indices.size(), + "MeshTools::duplicateInto(): index array and output size don't match, expected" << indices.size() << "but got" << out.size()[0], ); + CORRADE_ASSERT(data.isContiguous<1>() && out.isContiguous<1>(), + "MeshTools::duplicateInto(): second view dimension is not contiguous", ); + CORRADE_ASSERT(data.size()[1] == out.size()[1], + "MeshTools::duplicateInto(): input and output type size doesn't match, expected" << data.size()[1] << "but got" << out.size()[1], ); + const std::size_t size = data.size()[1]; + for(std::size_t i = 0; i != indices.size(); ++i) { + const std::size_t index = indices[i]; + CORRADE_ASSERT(index < data.size()[0], "MeshTools::duplicateInto(): index" << index << "out of bounds for" << data.size()[0] << "elements", ); + std::memcpy(out[i].data(), data[index].data(), size); + } +} + +} + +/* If not done this way but with templates instead, C++ wouldn't be able to + figure out on its own which overload to use when indices are not already a + strided arrray view */ +void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + duplicateIntoImplementation(indices, data, out); +} +void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + duplicateIntoImplementation(indices, data, out); +} +void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + duplicateIntoImplementation(indices, data, out); +} + +void duplicateInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::duplicateInto(): second index view dimension is not contiguous", ); + if(indices.size()[1] == 4) + return duplicateIntoImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), data, out); + else if(indices.size()[1] == 2) + return duplicateIntoImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), data, out); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::duplicateInto(): expected index type size 1, 2 or 4 but got" << indices.size()[1], ); + return duplicateIntoImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), data, out); + } +} + +Trade::MeshData duplicate(const Trade::MeshData& data, const Containers::ArrayView extra) { + CORRADE_ASSERT(data.isIndexed(), "MeshTools::duplicate(): mesh data not indexed", (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Calculate the layout */ + Trade::MeshData layout = interleavedLayout(data, data.indexCount(), extra); + + /* Copy existing attributes to new locations */ + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) + duplicateInto(data.indices(), data.attribute(i), layout.mutableAttribute(i)); + + /* Mix in the extra attributes */ + UnsignedInt attributeIndex = data.attributeCount(); + for(UnsignedInt i = 0; i != extra.size(); ++i) { + /* Padding, ignore */ + if(extra[i].format() == VertexFormat{}) continue; + + /* Asserting here even though data() has another assert since that one + would be too confusing in this context */ + CORRADE_ASSERT(!extra[i].isOffsetOnly(), + "MeshTools::duplicate(): extra attribute" << i << "is offset-only, which is not supported", + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Copy the attribute in, if it is non-empty, otherwise keep the + memory uninitialized */ + if(extra[i].data()) { + CORRADE_ASSERT(extra[i].data().size() == data.vertexCount(), + "MeshTools::duplicate(): extra attribute" << i << "expected to have" << data.vertexCount() << "items but got" << extra[i].data().size(), + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + const Containers::StridedArrayView2D attributeData = + Containers::arrayCast<2, const char>(extra[i].data(), vertexFormatSize(extra[i].format())); + duplicateInto(data.indices(), attributeData, layout.mutableAttribute(attributeIndex)); + } + + ++attributeIndex; + } + + return layout; +} + +Trade::MeshData duplicate(const Trade::MeshData& data, std::initializer_list extra) { + return duplicate(data, Containers::arrayView(extra)); +} + +}} diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 542e11dc75..80c1f255f5 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -29,13 +29,17 @@ * @brief Function @ref Magnum::MeshTools::duplicate(), @ref Magnum::MeshTools::duplicateInto() */ -#include #include -#include #include -#include #include "Magnum/Magnum.h" +#include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#endif namespace Magnum { namespace MeshTools { @@ -46,15 +50,16 @@ template void duplicateInto(const Containers::StridedA /** @brief Duplicate data using given index array +@m_since{2019,10} -Converts indexed array to non-indexed, for example data `{a, b, c, d}` with -index array `{1, 1, 0, 3, 2, 2}` will be converted to `{b, b, a, d, c, c}`. -The resulting array size is the same as size of @p indices, expects that all -indices are in range for the @p data array. +Converts indexed array to non-indexed, for example data @cpp {a, b, c, d} @ce +with index array @cpp {1, 1, 0, 3, 2, 2} @ce will be converted to +@cpp {b, b, a, d, c, c} @ce. The resulting array size is the same as size of +@p indices, expects that all indices are in range for the @p data array. If you want to fill an existing memory (or, for example a @ref std::vector), use @ref duplicateInto(). -@see @ref removeDuplicates(), @ref combineIndexedArrays() +@see @ref removeDuplicatesInPlace(), @ref combineIndexedAttributes() */ template Containers::Array duplicate(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data) { Containers::Array out{Containers::NoInit, indices.size()}; @@ -62,6 +67,19 @@ template Containers::Array duplicate(const Containe return out; } +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief Duplicate data using given index array +@m_deprecated_since_latest Use @ref duplicate(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + or @ref duplicateInto() instead. +*/ +template CORRADE_DEPRECATED("use duplicate() taking a StridedArrayView instead") std::vector duplicate(const std::vector& indices, const std::vector& data) { + std::vector out(indices.size()); + duplicateInto(indices, data, out); + return out; +} +#endif + /** @brief Duplicate data using an index array into given output array @param[in] indices Index array to use @@ -70,28 +88,77 @@ template Containers::Array duplicate(const Containe @m_since{2019,10} A variant of @ref duplicate() that fills existing memory instead of allocating -a new array. +a new array. Expects that @p out has the same size as @p indices and all +indices are in range for the @p data array. */ -template void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& out) { - CORRADE_ASSERT(out.size() == indices.size(), - "MeshTools::duplicateInto(): bad output size, expected" << indices.size() << "but got" << out.size(), ); - for(std::size_t i = 0; i != indices.size(); ++i) { - const std::size_t index = indices[i]; - CORRADE_ASSERT(index < data.size(), "MeshTools::duplicateInto(): index" << index << "out of bounds for" << data.size() << "elements", ); - out[i] = data[index]; - } -} +template void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& out); /** -@brief Duplicate data using given index array +@brief Duplicate type-erased data using an index array into given output array +@param[in] indices Index array to use +@param[in] data Input data +@param[out] out Where to store the output +@m_since_latest -Like @ref duplicate(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), -but putting the result into a @ref std::vector. +Compared to @ref duplicateInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +accepts a 2D view, where the second dimension spans the actual type. Expects +that @p out has the same size as @p indices and all indices are in range for +the @p data array, and that the second dimension of both @p data and @p out +is contiguous and has the same size. */ -template std::vector duplicate(const std::vector& indices, const std::vector& data) { - std::vector out(indices.size()); - duplicateInto(indices, data, out); - return out; +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +/** +@brief Duplicate type-erased data using a type-erased index array into given output array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref duplicateInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView2D&, const Containers::StridedArrayView2D&) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +/** +@brief Duplicate indexed mesh data +@m_since_latest + +Returns a copy of @p data that's not indexed and has all attributes interleaved +and duplicated according to @p data's index buffer. The @p extra attributes, if +any, are duplicated and interleaved together with existing attributes (or, in +case the attribute view is empty, only the corresponding space for given +attribute type is reserved, with memory left uninitialized). The data layouting +is done by @ref interleavedLayout(), see its documentation for detailed +behavior description. Note that offset-only @ref Trade::MeshAttributeData +instances are not supported in the @p extra array. + +Expects that @p data is indexed and each attribute in @p extra has either the +same amount of elements as @p data vertex count (*not* index count) or has +none. +@see @ref Trade::MeshData::attributeData() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData duplicate(const Trade::MeshData& data, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData duplicate(const Trade::MeshData& data, std::initializer_list extra); + +template inline void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& out) { + duplicateInto(indices, Containers::arrayCast<2, const char>(data), Containers::arrayCast<2, char>(out)); } }} diff --git a/src/Magnum/MeshTools/FlipNormals.cpp b/src/Magnum/MeshTools/FlipNormals.cpp index f6159135db..6adde3a47f 100644 --- a/src/Magnum/MeshTools/FlipNormals.cpp +++ b/src/Magnum/MeshTools/FlipNormals.cpp @@ -25,11 +25,16 @@ #include "FlipNormals.h" +#include +#include + #include "Magnum/Math/Vector3.h" namespace Magnum { namespace MeshTools { -void flipFaceWinding(std::vector& indices) { +namespace { + +template inline void flipFaceWindingInPlaceImplementation(const Containers::StridedArrayView1D& indices) { CORRADE_ASSERT(!(indices.size()%3), "MeshTools::flipNormals(): index count is not divisible by 3!", ); using std::swap; @@ -37,9 +42,49 @@ void flipFaceWinding(std::vector& indices) { swap(indices[i+1], indices[i+2]); } -void flipNormals(std::vector& normals) { +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices) { + flipFaceWindingInPlaceImplementation(indices); +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices) { + flipFaceWindingInPlaceImplementation(indices); +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices) { + flipFaceWindingInPlaceImplementation(indices); +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView2D& indices) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::flipFaceWindingInPlace(): second index view dimension is not contiguous", ); + if(indices.size()[1] == 4) + return flipFaceWindingInPlaceImplementation(Containers::arrayCast<1, UnsignedInt>(indices)); + else if(indices.size()[1] == 2) + return flipFaceWindingInPlaceImplementation(Containers::arrayCast<1, UnsignedShort>(indices)); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::flipFaceWindingInPlace(): expected index type size 1, 2 or 4 but got" << indices.size()[1], ); + return flipFaceWindingInPlaceImplementation(Containers::arrayCast<1, UnsignedByte>(indices)); + } +} + +void flipNormalsInPlace(const Containers::StridedArrayView1D& normals) { for(Vector3& normal: normals) normal = -normal; } +#ifdef MAGNUM_BUILD_DEPRECATED +void flipNormals(std::vector& indices, std::vector& normals) { + flipNormalsInPlace(indices, normals); +} + +void flipFaceWinding(std::vector& indices) { + flipFaceWindingInPlace(indices); +} + +void flipNormals(std::vector& normals) { + flipNormalsInPlace(normals); +} +#endif + }} diff --git a/src/Magnum/MeshTools/FlipNormals.h b/src/Magnum/MeshTools/FlipNormals.h index 6fad4a4a2a..65e67084c2 100644 --- a/src/Magnum/MeshTools/FlipNormals.h +++ b/src/Magnum/MeshTools/FlipNormals.h @@ -26,52 +26,146 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::flipFaceWinding(), @ref Magnum::MeshTools::flipNormals() + * @brief Function @ref Magnum::MeshTools::flipFaceWindingInPlace(), @ref Magnum::MeshTools::flipNormalsInPlace() */ -#include +#include #include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#include +#endif + namespace Magnum { namespace MeshTools { /** -@brief Flip face winding +@brief Flip mesh normals and face winding in-place @param[in,out] indices Index array to operate on +@param[in,out] normals Normal array to operate on -The same as @ref flipNormals(std::vector&, std::vector&), -but flips only face winding. - -@attention The function requires the mesh to have triangle faces, thus index - count must be divisible by 3. +Flips normal vectors and face winding in index array for face culling to work +properly too. See also @ref flipNormalsInPlace(const Containers::StridedArrayView1D&) +and @ref flipFaceWindingInPlace(), which flip normals or face winding only. +Expects a triangle mesh, thus the index count has to be divisible by 3. */ -void MAGNUM_MESHTOOLS_EXPORT flipFaceWinding(std::vector& indices); +void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); /** -@brief Flip mesh normals -@param[in,out] normals Normal array to operate on + * @overload + * @m_since_latest + */ +void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); -The same as @ref flipNormals(std::vector&, std::vector&), -but flips only normals, not face winding. +/** + * @overload + * @m_since_latest + */ +void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); + +/** +@brief Flip mesh normals and face winding in-place on a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +etc. overloads. +*/ +void flipNormalsInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& normals); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief @copybrief flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +@m_deprecated_since_latest Use @ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + instead. */ -void MAGNUM_MESHTOOLS_EXPORT flipNormals(std::vector& normals); +CORRADE_DEPRECATED("use flipNormalsInPlace() instead") MAGNUM_MESHTOOLS_EXPORT void flipNormals(std::vector& indices, std::vector& normals); +#endif /** -@brief Flip mesh normals and face winding +@brief Flip face winding in-place @param[in,out] indices Index array to operate on +@m_since_latest + +Same as @ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), +but flips only face winding. Expects a triangle mesh, thus the index count has +to be divisible by 3. +*/ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); + +/** + * @overload + * @m_since_latest + */ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); + +/** + * @overload + * @m_since_latest + */ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); + +/** +@brief Flip face winding in-place on a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref flipFaceWindingInPlace(const Containers::StridedArrayView1D&) +etc. overloads. +*/ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView2D& indices); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief @copybrief flipFaceWindingInPlace(const Containers::StridedArrayView1D&) +@m_deprecated_since_latest Use @ref flipFaceWindingInPlace(const Containers::StridedArrayView1D&) + instead. +*/ +CORRADE_DEPRECATED("use flipFaceWindingInPlace() instead") MAGNUM_MESHTOOLS_EXPORT void flipFaceWinding(std::vector& indices); +#endif + +/** +@brief Flip mesh normals in-place @param[in,out] normals Normal array to operate on -Flips normal vectors and face winding in index array for face culling to work -properly too. See also @ref flipNormals(std::vector&) and -@ref flipFaceWinding(), which flip normals or face winding only. +Same as @ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), +but flips only normals, not face winding. +*/ +void MAGNUM_MESHTOOLS_EXPORT flipNormalsInPlace(const Containers::StridedArrayView1D& normals); -@attention The function requires the mesh to have triangle faces, thus index - count must be divisible by 3. +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@copybrief flipNormalsInPlace(const Containers::StridedArrayView1D&) +@m_deprecated_since_latest Use @ref flipNormalsInPlace(const Containers::StridedArrayView1D&) + instead. */ -inline void flipNormals(std::vector& indices, std::vector& normals) { - flipFaceWinding(indices); - flipNormals(normals); +CORRADE_DEPRECATED("use flipNormalsInPlace() instead") MAGNUM_MESHTOOLS_EXPORT void flipNormals(std::vector& normals); +#endif + +inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); +} + +inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); +} + +inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); +} + +inline void flipNormalsInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); } }} diff --git a/src/Magnum/MeshTools/GenerateNormals.cpp b/src/Magnum/MeshTools/GenerateNormals.cpp index b6f9142f1e..ce6e11151c 100644 --- a/src/Magnum/MeshTools/GenerateNormals.cpp +++ b/src/Magnum/MeshTools/GenerateNormals.cpp @@ -81,11 +81,15 @@ std::pair, std::vector> generateFlatNormals(co } /* Remove duplicate normals and return */ + CORRADE_IGNORE_DEPRECATED_PUSH normalIndices = MeshTools::duplicate(normalIndices, MeshTools::removeDuplicates(normals)); + CORRADE_IGNORE_DEPRECATED_POP return {std::move(normalIndices), std::move(normals)}; } #endif +namespace { + #if defined(CORRADE_MSVC2019_COMPATIBILITY) && !defined(CORRADE_MSVC2017_COMPATIBILITY) /* When using /permissive- with MSVC2019, using namespace inside the function below FOR SOME REASON gets lost when instantiating the template. That's @@ -94,7 +98,7 @@ std::pair, std::vector> generateFlatNormals(co using namespace Math::Literals; #endif -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { +template inline void generateSmoothNormalsIntoImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { CORRADE_ASSERT(indices.size() % 3 == 0, "MeshTools::generateSmoothNormalsInto(): index count not divisible by 3", ); CORRADE_ASSERT(normals.size() == positions.size(), @@ -109,7 +113,7 @@ template void generateSmoothNormalsInto(const Containers::StridedArrayV Containers::arrayCast(normals); for(UnsignedInt& i: triangleCount) i = 0; for(const T index: indices) { - CORRADE_ASSERT(index < positions.size(), "MeshTools::generateSmoothNormals(): index" << index << "out of bounds for" << positions.size() << "elements", ); + CORRADE_ASSERT(index < positions.size(), "MeshTools::generateSmoothNormalsInto(): index" << index << "out of bounds for" << positions.size() << "elements", ); ++triangleCount[index]; } @@ -217,22 +221,60 @@ template void generateSmoothNormalsInto(const Containers::StridedArrayV } } -#ifndef DOXYGEN_GENERATING_OUTPUT -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +} + +/* If not done this way but with templates instead, C++ wouldn't be able to + figure out on its own which overload to use when indices are not already a + strided arrray view */ +void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + generateSmoothNormalsIntoImplementation(indices, positions, normals); +} +void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + generateSmoothNormalsIntoImplementation(indices, positions, normals); +} +void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + generateSmoothNormalsIntoImplementation(indices, positions, normals); +} + +void generateSmoothNormalsInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::generateSmoothNormalsInto(): second index view dimension is not contiguous", ); + if(indices.size()[1] == 4) + return generateSmoothNormalsIntoImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), positions, normals); + else if(indices.size()[1] == 2) + return generateSmoothNormalsIntoImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), positions, normals); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::generateSmoothNormalsInto(): expected index type size 1, 2 or 4 but got" << indices.size()[1], ); + return generateSmoothNormalsIntoImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), positions, normals); + } +} + +namespace { -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { +template inline Containers::Array generateSmoothNormalsImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { Containers::Array out{Containers::NoInit, positions.size()}; generateSmoothNormalsInto(indices, positions, out); return out; } -#ifndef DOXYGEN_GENERATING_OUTPUT -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +} + +/* If not done this way but with templates instead, C++ wouldn't be able to + figure out on its own which overload to use when indices are not already a + strided arrray view */ +Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { + return generateSmoothNormalsImplementation(indices, positions); +} +Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { + return generateSmoothNormalsImplementation(indices, positions); +} +Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { + return generateSmoothNormalsImplementation(indices, positions); +} + +Containers::Array generateSmoothNormals(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions) { + Containers::Array out{Containers::NoInit, positions.size()}; + generateSmoothNormalsInto(indices, positions, out); + return out; +} }} diff --git a/src/Magnum/MeshTools/GenerateNormals.h b/src/Magnum/MeshTools/GenerateNormals.h index 5d73a461c4..219bdfe737 100644 --- a/src/Magnum/MeshTools/GenerateNormals.h +++ b/src/Magnum/MeshTools/GenerateNormals.h @@ -43,11 +43,12 @@ namespace Magnum { namespace MeshTools { @brief Generate flat normals @param positions Triangle vertex positions @return Per-vertex normals +@m_since{2019,10} All vertices in each triangle face get the same normal vector. Expects that the position count is divisible by 3. If you need to generate flat normals for an indexed mesh, @ref duplicate() the vertices first, after the operation you -might want to remove the duplicates again using @ref removeDuplicates(). +might want to remove the duplicates again using @ref removeDuplicatesInPlace(). Example usage: @snippet MagnumMeshTools.cpp generateFlatNormals @@ -117,19 +118,37 @@ Martijn Buijs. @see @ref generateSmoothNormalsInto(), @ref generateFlatNormals(), @ref MeshTools::CompileFlag::GenerateSmoothNormals */ -template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); -#if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) -extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); + +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); + +/** +@brief Generate smooth normals using a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions); /** @brief Generate smooth normals into an existing array @param[in] indices Triangle face indices @param[in] positions Triangle vertex positions @param[out] normals Where to put the generated normals +@m_since{2019,10} A variant of @ref generateSmoothNormals() that fills existing memory instead of allocating a new array. The @p normals array is expected to have the same size @@ -145,13 +164,31 @@ case @cpp #include @ce @ref Corrade/Containers/ArrayViewStl.h to get implicit @see @ref generateFlatNormalsInto() */ -template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); -#if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) -extern template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); + +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); + +/** +@brief Generate smooth normals into an existing array using a type-erased index array +@m_since_latest + +Expects that @p normals has the same size as @p positions and that the second +dimension of @p indices is contiguous and represents the actual 1/2/4-byte +index type. Based on its size then calls one of the +@ref generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); }} diff --git a/src/Magnum/MeshTools/Implementation/Tipsify.h b/src/Magnum/MeshTools/Implementation/Tipsify.h index 63def0dcd8..214e385b0a 100644 --- a/src/Magnum/MeshTools/Implementation/Tipsify.h +++ b/src/Magnum/MeshTools/Implementation/Tipsify.h @@ -25,7 +25,8 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include +#include #include "Magnum/Magnum.h" @@ -33,11 +34,10 @@ namespace Magnum { namespace MeshTools { namespace Implementation { namespace { /* Vertex-triangle adjacency. Computes count and indices of adjacent triangles for each vertex (used internally by tipsify()) */ -void buildAdjacency(const std::vector& indices, const UnsignedInt vertexCount, std::vector& liveTriangleCount, std::vector& neighborOffset, std::vector& neighbors) { +template void buildAdjacency(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, Containers::Array& liveTriangleCount, Containers::Array& neighborOffset, Containers::Array& neighbors) { /* How many times is each vertex referenced == count of neighboring triangles for each vertex */ - liveTriangleCount.clear(); - liveTriangleCount.resize(vertexCount); + liveTriangleCount = Containers::Array{vertexCount}; for(std::size_t i = 0; i != indices.size(); ++i) ++liveTriangleCount[indices[i]]; @@ -45,19 +45,17 @@ void buildAdjacency(const std::vector& indices, const UnsignedInt v the end be in interval neighbors[neighborOffset[i]] ; neighbors[neighborOffset[i+1]]. Currently the values are shifted to right, because the next loop will shift them back left. */ - neighborOffset.clear(); - neighborOffset.reserve(vertexCount+1); - neighborOffset.push_back(0); + neighborOffset = Containers::Array{Containers::NoInit, vertexCount + 1}; + neighborOffset[0] = 0; UnsignedInt sum = 0; for(std::size_t i = 0; i != vertexCount; ++i) { - neighborOffset.push_back(sum); + neighborOffset[i + 1] = sum; sum += liveTriangleCount[i]; } /* Array of neighbors, using (and changing) neighborOffset array for positioning */ - neighbors.clear(); - neighbors.resize(sum); + neighbors = Containers::Array{Containers::NoInit, sum}; for(std::size_t i = 0; i != indices.size(); ++i) neighbors[neighborOffset[indices[i]+1]++] = i/3; } diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp new file mode 100644 index 0000000000..913a16a675 --- /dev/null +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -0,0 +1,315 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "Interleave.h" + +#include + +#include "Magnum/Math/Functions.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { + +namespace { + +Containers::StridedArrayView2D interleavedDataInternal(const Trade::MeshData& data) { + /* There is no attributes, return a non-nullptr zero-sized view to indicate + a success */ + if(!data.attributeCount() || !data.vertexData()) + return Containers::StridedArrayView2D{data.vertexData(), {data.vertexCount(), 0}}; + + const UnsignedInt stride = data.attributeStride(0); + std::size_t minOffset = data.attributeOffset(0); + std::size_t maxOffset = minOffset + vertexFormatSize(data.attributeFormat(0)); + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) { + if(data.attributeStride(i) != stride) return nullptr; + + const std::size_t offset = data.attributeOffset(i); + minOffset = Math::min(minOffset, offset); + maxOffset = Math::max(maxOffset, offset + vertexFormatSize(data.attributeFormat(i))); + } + + /* The offsets can't fit into the stride, report failure */ + if(maxOffset - minOffset > stride) return nullptr; + + return Containers::StridedArrayView2D{ + data.vertexData(), data.vertexData().data() + minOffset, + {data.vertexCount(), maxOffset - minOffset}, + {std::ptrdiff_t(stride), 1}}; +} + +} + +bool isInterleaved(const Trade::MeshData& data) { + /* If a nullptr value is returned but the mesh vertex data is not nullptr, + the mesh is not interleaved. Otherwise it is */ + return !!interleavedDataInternal(data).data() == !!data.vertexData().data(); +} + +Containers::StridedArrayView2D interleavedData(const Trade::MeshData& data) { + auto out = interleavedDataInternal(data); + CORRADE_ASSERT(out || !data.vertexData(), + "MeshTools::interleavedData(): the mesh is not interleaved", {}); + return out; +} + +namespace Implementation { + +Containers::Array interleavedLayout(Trade::MeshData&& data, const Containers::ArrayView extra) { + /* Nothing to do here, bye! */ + if(!data.attributeCount() && extra.empty()) return {}; + + const bool interleaved = isInterleaved(data); + + /* If the mesh is already interleaved, use the original stride to + preserve all padding, but remove the initial offset. Otherwise calculate + a tightly-packed stride. */ + std::size_t stride; + std::size_t minOffset; + if(interleaved && data.attributeCount()) { + stride = data.attributeStride(0); + minOffset = ~std::size_t{}; + for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) + minOffset = Math::min(minOffset, data.attributeOffset(i)); + } else { + stride = 0; + minOffset = 0; + for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) + stride += vertexFormatSize(data.attributeFormat(i)); + } + + /* Add the extra attributes and explicit padding */ + std::size_t extraAttributeCount = 0; + for(std::size_t i = 0; i != extra.size(); ++i) { + if(extra[i].format() == VertexFormat{}) { + CORRADE_ASSERT(extra[i].stride() > 0 || stride >= std::size_t(-extra[i].stride()), + "MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, {}); + stride += extra[i].stride(); + } else { + stride += vertexFormatSize(extra[i].format()); + ++extraAttributeCount; + } + } + + /* Transfer the attribute data array. If there are no extra attributes and + the attribute data array is owned (the array has a default deleter), we + can take over the ownership and avoid an allocation. Otherwise we + allocate a new array and copy the prefix over so we can just patch the + data array later. */ + const UnsignedInt originalAttributeCount = data.attributeCount(); + const UnsignedInt originalAttributeStride = originalAttributeCount ? + data.attributeStride(0) : 0; + Containers::Array originalAttributeData = + data.releaseAttributeData(); + Containers::Array attributeData; + if(!extraAttributeCount && !originalAttributeData.deleter()) + attributeData = std::move(originalAttributeData); + else { + attributeData = Containers::Array{originalAttributeCount + extraAttributeCount}; + Utility::copy(originalAttributeData, attributeData.prefix(originalAttributeCount)); + } + + /* Copy existing attribute layout. If the original is already interleaved, + preserve relative attribute offsets, otherwise pack tightly. */ + std::size_t offset = 0; + for(UnsignedInt i = 0; i != originalAttributeCount; ++i) { + if(interleaved) offset = attributeData[i].offset(data.vertexData()) - minOffset; + + attributeData[i] = Trade::MeshAttributeData{ + attributeData[i].name(), attributeData[i].format(), + offset, 0, std::ptrdiff_t(stride)}; + + if(!interleaved) offset += vertexFormatSize(attributeData[i].format()); + } + + /* In case the original is already interleaved, set the offset for extra + attribs to the original stride to preserve also potential padding at the + end. */ + if(interleaved && originalAttributeCount) + offset = originalAttributeStride; + + /* Mix in the extra attributes */ + UnsignedInt attributeIndex = originalAttributeCount; + for(UnsignedInt i = 0; i != extra.size(); ++i) { + /* Padding, only adjust the offset for next attribute */ + if(extra[i].format() == VertexFormat{}) { + offset += extra[i].stride(); + continue; + } + + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + extra[i].name(), extra[i].format(), + offset, 0, std::ptrdiff_t(stride)}; + + offset += vertexFormatSize(extra[i].format()); + } + + return attributeData; +} + +} + +Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { + Containers::Array attributeData = Implementation::interleavedLayout(std::move(data), extra); + + /* If there are no attributes, bail -- return an empty mesh with desired + vertex count but nothing else */ + if(!attributeData) + return Trade::MeshData{data.primitive(), vertexCount}; + + /* Allocate new data array */ + Containers::Array vertexData{Containers::NoInit, attributeData[0].stride()*vertexCount}; + + /* Convert the attributes from offset-only and zero vertex count to + absolute, referencing the above-allocated data array */ + for(Trade::MeshAttributeData& attribute: attributeData) { + attribute = Trade::MeshAttributeData{ + attribute.name(), attribute.format(), + Containers::StridedArrayView1D{vertexData, + vertexData + attribute.offset(vertexData), + vertexCount, attribute.stride()}}; + } + + return Trade::MeshData{data.primitive(), std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const std::initializer_list extra) { + return interleavedLayout(std::move(data), vertexCount, Containers::arrayView(extra)); +} + +Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { + return interleavedLayout( + Trade::MeshData{data.primitive(), {}, data.vertexData(), + Trade::meshAttributeDataNonOwningArray(data.attributeData()), + data.vertexCount()}, + vertexCount, extra); +} + +Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const std::initializer_list extra) { + return interleavedLayout(data, vertexCount, Containers::arrayView(extra)); +} + +Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView extra) { + /* If there are no attributes and no index buffer, bail -- the vertex count + is the only property we can transfer. If this wouldn't be done, the + return at the end would assert as vertex count is only passed implicitly + via attributes (which there are none). */ + if(!data.attributeCount() && extra.empty() && !data.isIndexed()) + return Trade::MeshData{data.primitive(), data.vertexCount()}; + + /* Transfer the indices unchanged, in case the mesh is indexed */ + Containers::Array indexData; + Trade::MeshIndexData indices; + if(data.isIndexed()) { + /* If we can steal the data, do it */ + if(data.indexDataFlags() & Trade::DataFlag::Owned) { + indices = Trade::MeshIndexData{data.indices()}; + indexData = data.releaseIndexData(); + } else { + indexData = Containers::Array{data.indexData().size()}; + Utility::copy(data.indexData(), indexData); + indices = Trade::MeshIndexData{data.indexType(), + Containers::ArrayView{indexData + data.indexOffset(), data.indices().size()[0]*data.indices().size()[1]}}; + } + } + + const bool interleaved = isInterleaved(data); + + /* If the mesh is already interleaved and we don't have anything extra, + steal that data as well */ + Containers::Array vertexData; + Containers::Array attributeData; + if(interleaved && extra.empty() && (data.vertexDataFlags() & Trade::DataFlag::Owned)) { + attributeData = data.releaseAttributeData(); + vertexData = data.releaseVertexData(); + + /* Otherwise do it the hard way */ + } else { + /* Calculate the layout */ + Trade::MeshData layout = interleavedLayout(data, data.vertexCount(), extra); + + /* Copy existing attributes to new locations */ + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) + Utility::copy(data.attribute(i), layout.mutableAttribute(i)); + + /* Mix in the extra attributes */ + UnsignedInt attributeIndex = data.attributeCount(); + for(UnsignedInt i = 0; i != extra.size(); ++i) { + /* Padding, ignore */ + if(extra[i].format() == VertexFormat{}) continue; + + /* Asserting here even though data() has another assert since that + one would be too confusing in this context */ + CORRADE_ASSERT(!extra[i].isOffsetOnly(), + "MeshTools::interleave(): extra attribute" << i << "is offset-only, which is not supported", + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Copy the attribute in, if it is non-empty, otherwise keep the + memory uninitialized */ + if(extra[i].data()) { + CORRADE_ASSERT(extra[i].data().size() == data.vertexCount(), + "MeshTools::interleave(): extra attribute" << i << "expected to have" << data.vertexCount() << "items but got" << extra[i].data().size(), + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + const Containers::StridedArrayView2D attribute = + Containers::arrayCast<2, const char>(extra[i].data(), vertexFormatSize(extra[i].format())); + Utility::copy(attribute, layout.mutableAttribute(attributeIndex)); + } + + ++attributeIndex; + } + + /* Release the data from the layout to pack them into the output */ + vertexData = layout.releaseVertexData(); + attributeData = layout.releaseAttributeData(); + } + + return Trade::MeshData{data.primitive(), std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData interleave(Trade::MeshData&& data, const std::initializer_list extra) { + return interleave(std::move(data), Containers::arrayView(extra)); +} + +Trade::MeshData interleave(const Trade::MeshData& data, const Containers::ArrayView extra) { + Containers::ArrayView indexData; + Trade::MeshIndexData indices; + if(data.isIndexed()) { + indexData = data.indexData(); + indices = Trade::MeshIndexData{data.indices()}; + } + + return interleave(Trade::MeshData{data.primitive(), + {}, indexData, indices, + {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData()), + data.vertexCount() + }, extra); +} + +Trade::MeshData interleave(const Trade::MeshData& data, const std::initializer_list extra) { + return interleave(std::move(data), Containers::arrayView(extra)); +} + +}} diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index c95b19afda..9c73e64c81 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::interleave(), @ref Magnum::MeshTools::interleaveInto() + * @brief Function @ref Magnum::MeshTools::interleave(), @ref Magnum::MeshTools::interleaveInto(), @ref Magnum::MeshTools::isInterleaved(), @ref Magnum::MeshTools::interleavedLayout() */ #include @@ -35,6 +35,8 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" namespace Magnum { namespace MeshTools { @@ -112,13 +114,17 @@ template void writeInterleaved(std::size_t stride, char* st writeInterleaved(stride, startingOffset + writeOneInterleaved(stride, startingOffset, first), next...); } +/* Used internally by interleavedLayout() and concatenate() */ +MAGNUM_MESHTOOLS_EXPORT Containers::Array interleavedLayout(Trade::MeshData&& data, Containers::ArrayView extra); + } /** @brief Interleave vertex attributes This function takes list of attribute arrays and returns them interleaved, so -data for each attribute are in continuous place in memory. +data for each attribute are in continuous place in memory. Expects that all +attributes have the same element count. Example usage: @@ -132,18 +138,21 @@ achieve that, you can specify gaps between the attributes: All gap bytes are set zero. This way vertex stride is 24 bytes, without gaps it would be 21 bytes, causing possible performance loss. -@attention The function expects that all arrays have the same size. - -@note The only requirements to attribute array type is that it must have either - a typedef `T::Type` (in case of Corrade types such as - @ref Corrade::Containers::ArrayView) or a typedef `T::value_type` (in case - of STL types such as @ref std::vector or @ref std::array) or a, a forward - iterator (to be used with range-based for) and a function `size()` - returning count of elements. +@note The only requirements to the attribute array type is that it must have + either a typedef @cpp T::Type @ce (in case of Corrade types such as + @ref Corrade::Containers::ArrayView) or a typedef @cpp T::value_type @ce + (in case of STL types such as @ref std::vector or @ref std::array), a + forward iterator (to be used with range-based for) and a @cpp size() @ce + method returning element count. @see @ref interleaveInto() */ -template Containers::Array interleave(const T& first, const U&... next) +template::value>::type + #endif +> Containers::Array interleave(const T& first, const U&... next) { /* Compute buffer size and stride */ const std::size_t attributeCount = Implementation::AttributeCount{}(first, next...); @@ -166,11 +175,8 @@ template Containers::Array interleave(const T& first, Unlike @ref interleave() this function interleaves the data into existing buffer and leaves gaps untouched instead of zero-initializing them. This function can thus be used for interleaving data depending on runtime -parameters. - -@attention Similarly to @ref interleave(), this function expects that all - arrays have the same size. The passed buffer must also be large enough to - contain the interleaved data. +parameters. Expects that all arrays have the same size and the passed buffer is +large enough to contain the interleaved data. */ template void interleaveInto(Containers::ArrayView buffer, const T& first, const U&... next) { /* Verify expected buffer size */ @@ -184,6 +190,136 @@ template void interleaveInto(Containers::ArrayView bu Implementation::writeInterleaved(stride, buffer.begin(), first, next...); } +/** +@brief If the mesh data is interleaved +@m_since_latest + +Returns @cpp true @ce if all attributes have the same stride and the difference +between minimal and maximal offset is not larger than the stride, @cpp false @ce +otherwise. In particular, returns @cpp true @ce also if the mesh has just one +or no attributes. +@see @ref Trade::MeshData::attributeStride(), + @ref Trade::MeshData::attributeOffset(), @ref interleavedData() +*/ +MAGNUM_MESHTOOLS_EXPORT bool isInterleaved(const Trade::MeshData& data); + +/** +@brief Type-erased view on interleaved mesh data +@m_since_latest + +Returns a 2D view on @ref Trade::MeshData::vertexData() that spans all +interleaved attributes, with the first dimension being the vertex count and the +second being the attribute stride that's common for all attributes. Expects +that the mesh is interleaved. +@see @ref isInterleaved() +*/ +MAGNUM_MESHTOOLS_EXPORT Containers::StridedArrayView2D interleavedData(const Trade::MeshData& data); + +/** +@brief Create an interleaved mesh layout +@m_since_latest + +Returns a @ref Trade::MeshData instance with its vertex data allocated for +@p vertexCount vertices containing attributes from both @p data and @p extra +interleaved together. No data is actually copied, only an interleaved layout is +created. If @p data is already interleaved, keeps the attributes in the same +layout, potentially extending them with @p extra. The @p extra attributes, if +any, are interleaved together with existing attributes. Returned instance +vertex data flags have both @ref Trade::DataFlag::Mutable and @ref Trade::DataFlag::Owned, so mutable attribute access is guaranteed. + +For greater control you can also pass an empty @ref Trade::MeshData instance +and fill @p extra with attributes cherry-picked using +@ref Trade::MeshData::attributeData(UnsignedInt) const on an existing instance. +By default the attributes are tightly packed, you can add arbitrary padding +using instances constructed via +@ref Trade::MeshAttributeData::MeshAttributeData(Int). Example: + +@snippet MagnumMeshTools.cpp interleavedLayout-extra + +This function doesn't preserve index data information in any way, making the +output non-indexed. If you want to preserve index data, create a new indexed +instance with attribute and vertex data transferred from the returned instance: + +@snippet MagnumMeshTools.cpp interleavedLayout-indices + +This function will unconditionally allocate a new array to store all +@ref Trade::MeshAttributeData, use @ref interleavedLayout(Trade::MeshData&&, UnsignedInt, Containers::ArrayView) +to avoid that allocation. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, std::initializer_list extra); + +/** +@brief Create an interleaved mesh layout +@m_since_latest + +Compared to @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView) +this function can reuse the @ref Trade::MeshAttributeData array from @p data +instead of allocating a new one if there are no attributes passed in @p extra +and the attribute array is owned by the mesh. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(Trade::MeshData&& data, UnsignedInt vertexCount, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(Trade::MeshData&& data, UnsignedInt vertexCount, std::initializer_list extra); + +/** +@brief Interleave mesh data +@m_since_latest + +Returns a copy of @p data with all attributes interleaved. Indices (if any) are +kept as-is. The @p extra attributes, if any, are interleaved together with +existing attributes (or, in case the attribute view is empty, only the +corresponding space for given attribute type is reserved, with memory left +uninitialized). The data layouting is done by @ref interleavedLayout(), see its +documentation for detailed behavior description. Note that offset-only +@ref Trade::MeshAttributeData instances are not supported in the @p extra +array. + +Expects that each attribute in @p extra has either the same amount of elements +as @p data vertex count or has none. This function will unconditionally make a +copy of all data even if @p data is already interleaved and needs no change, +use @ref interleave(Trade::MeshData&&, Containers::ArrayView) +to avoid that copy. +@see @ref isInterleaved(), @ref Trade::MeshData::attributeData() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, std::initializer_list extra); + +/** +@brief Interleave mesh data +@m_since_latest + +Compared to @ref interleave(const Trade::MeshData&, Containers::ArrayView) +this function can transfer ownership of @p data index buffer (in case it is +owned) and vertex buffer (in case it is owned, already interleaved and there's +no @p extra attributes) to the returned instance instead of making copies of +them. +@see @ref isInterleaved(), @ref Trade::MeshData::indexDataFlags(), + @ref Trade::MeshData::vertexDataFlags(), + @ref Trade::MeshData::attributeData() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(Trade::MeshData&& data, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(Trade::MeshData&& data, std::initializer_list extra); + }} #endif diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp new file mode 100644 index 0000000000..965ee0a283 --- /dev/null +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -0,0 +1,160 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include +#include + +#include "RemoveDuplicates.h" + +namespace Magnum { namespace MeshTools { + +struct ArrayEqual { + bool operator()(Containers::ArrayView a, Containers::ArrayView b) const { + CORRADE_INTERNAL_ASSERT(a.size() == b.size()); + return std::memcmp(a, b, a.size()) == 0; + } +}; + +struct ArrayHash { + std::size_t operator()(Containers::ArrayView a) const { + return *reinterpret_cast(Utility::MurmurHash2{}(a, a.size()).byteArray()); + } +}; + +std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { + /* Assuming the second dimension is contiguous so we can calculate the + hashes easily */ + CORRADE_ASSERT(data.empty()[0] || data.isContiguous<1>(), + "MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous", {}); + + const std::size_t dataSize = data.size()[0]; + CORRADE_ASSERT(indices.size() == dataSize, + "MeshTools::removeDuplicatesInto(): output index array has" << indices.size() << "elements but expected" << dataSize, {}); + + /* Table containing index of first occurence for each unique entry. + Reserving more buckets than necessary (i.e. as if each entry was + unique). */ + std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; + + /* Go through all entries */ + for(std::size_t i = 0; i != dataSize; ++i) { + /* Try to insert new entry into the table. The inserted index points + into the original unchanged data array. */ + const Containers::ArrayView entry = data[i].asContiguous(); + const auto result = table.emplace(entry, i); + + /* Put the (either new or already existing) index into the output + index array */ + indices[i] = result.first->second; + } + + CORRADE_INTERNAL_ASSERT(dataSize >= table.size()); + return table.size(); +} + +std::pair, std::size_t> removeDuplicates(const Containers::StridedArrayView2D& data) { + Containers::Array indices{Containers::NoInit, data.size()[0]}; + const std::size_t size = removeDuplicatesInto(data, indices); + return {std::move(indices), size}; +} + +std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { + /* Assuming the second dimension is contiguous so we can calculate the + hashes easily */ + CORRADE_ASSERT(data.empty()[0] || data.isContiguous<1>(), + "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous", {}); + + const std::size_t dataSize = data.size()[0]; + CORRADE_ASSERT(indices.size() == dataSize, + "MeshTools::removeDuplicatesInPlaceInto(): output index array has" << indices.size() << "elements but expected" << dataSize, {}); + + /* Table containing index of first occurence for each unique entry. + Reserving more buckets than necessary (i.e. as if each entry was + unique). */ + std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; + + /* Go through all entries */ + for(std::size_t i = 0; i != dataSize; ++i) { + /* Try to insert new entry into the table. The inserted index points + into the new data array that has all duplicates removed. */ + const Containers::ArrayView entry = data[i].asContiguous(); + const auto result = table.emplace(entry, table.size()); + + /* Put the (either new or already existing) index into the output index + array */ + indices[i] = result.first->second; + + /* If this is a new combination, copy the data to new (earlier) + position in the array. Data in [table.size()-1, i) are already + present in the [0, table.size()-1) range from previous iterations so + we aren't overwriting anything. */ + if(result.second && i != table.size() - 1) + Utility::copy(entry, data[table.size() - 1].asContiguous()); + } + + CORRADE_INTERNAL_ASSERT(dataSize >= table.size()); + return table.size(); +} + +std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data) { + Containers::Array indices{Containers::NoInit, data.size()[0]}; + const std::size_t size = removeDuplicatesInPlaceInto(data, indices); + return {std::move(indices), size}; +} + +namespace { + +template std::size_t removeDuplicatesIndexedInPlaceImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is + int(-1) instead of the type I want */ + CORRADE_ASSERT(data.size()[0] <= IndexType(-1), + "MeshTools::removeDuplicatesIndexedInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << data.size()[0] << "vertices", {}); + + /* There's no way to avoid the additional allocation, unfortunately --- + iterating over the indices instead of data would not preserve the + original order, which is an useful property. The float version has this + inverted (having the *Indexed() variant as the main implementation) + because the remapping there has to be done once for every dimension. */ + std::pair, std::size_t> result = removeDuplicatesInPlace(data); + for(auto& i: indices) i = result.first[i]; + return result.second; +} + +} + +std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + return removeDuplicatesIndexedInPlaceImplementation(indices, data); +} + +std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + return removeDuplicatesIndexedInPlaceImplementation(indices, data); +} + +std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + return removeDuplicatesIndexedInPlaceImplementation(indices, data); +} + +}} diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index 2f5278c9ed..b284282cc2 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -26,18 +26,20 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::removeDuplicates() + * @brief Function @ref Magnum::MeshTools::removeDuplicatesInPlace(), @ref Magnum::MeshTools::removeDuplicatesIndexedInPlace() */ #include #include #include #include +#include #include #include #include "Magnum/Magnum.h" #include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/MeshTools/visibility.h" namespace Magnum { namespace MeshTools { @@ -51,94 +53,245 @@ namespace Implementation { } /** -@brief Remove duplicate floating-point vector data from given array -@param[in,out] data Input data array -@param[in] epsilon Epsilon value, vertices nearer than this distance will be +@brief Remove duplicate data from given array in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@return The resulting index array and size of unique prefix in the cleaned up + @p data array +@m_since_latest + +Removes duplicate data from given array by comparing the second dimension of +each item, the second dimension is expected to be contiguous. A plain bit-exact +matching is used, if you need fuzzy comparison for floating-point data, use +@ref removeDuplicatesInPlace(const Containers::StridedArrayView1D&, typename Vector::Type) +instead. If you want to remove duplicate data from an already indexed array, +use @ref removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView2D&) +instead. Usage example: + +@snippet MagnumMeshTools.cpp removeDuplicates + +See @ref removeDuplicates(const Containers::StridedArrayView2D&) +for a variant that doesn't modify the input data in any way but instead returns +an index array pointing to original data locations. +@see @ref Corrade::Containers::StridedArrayView::isContiguous(), + @ref removeDuplicatesInPlaceInto() +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data); + +/** +@brief Remove duplicate data from given array in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@param[out] indices Where to put the resulting index array +@return Size of unique prefix in the cleaned up @p data array +@m_since_latest + +Same as above, except that the index array is not allocated but put into +@p indices instead. Expects that @p indices has the same size as @p data. +@see @ref removeDuplicatesInto() +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); + +/** +@brief Remove duplicate data from given array +@param[in] data Data array +@return The resulting index array and count of unique items in the original + @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +this function doesn't modify the input data array in any way but instead +returns an index array pointing to original data locations. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicates(const Containers::StridedArrayView2D& data); + +/** +@brief Remove duplicate data from given array +@param[in] data Data array +@param[out] indices Where to put the resulting index array +@return Count of unique items in the original @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D&, const Containers::StridedArrayView1D&) +this function doesn't modify the input data array in any way but instead +makes an index array pointing to original data locations. +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); + +/** +@brief Remove duplicates from indexed data in-place +@param[in,out] indices Index array, which will get remapped to list just + unique data +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@return Size of unique prefix in the cleaned up @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +this variant is more suited for data that is already indexed as it works on +the existing index array instead of allocating a new one. +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); + +/** +@brief Remove duplicate floating-point vector data from given array in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@param[in] epsilon Epsilon value, vertices closer than this distance will be melt together -@return Index array and unique data +@return Size of unique prefix in the cleaned up @p data array and the resulting + index array +@m_since_latest Removes duplicate data from the array by collapsing them into buckets of size @p epsilon. First vector in given bucket is used, other ones are thrown away, no interpolation is done. Note that this function is meant to be used for -floating-point data (or generally with non-zero @p epsilon), for discrete data -the usual sorting method is much more efficient. +floating-point data (or generally with non-zero @p epsilon), for data where +bit-exact matching is sufficient use @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +instead. + +If you want to remove duplicate data from an already indexed array, use +@ref removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, typename Vector::Type) instead. + +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(). +*/ +template std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief Remove duplicate floating-point vector data from a STL vector in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved and the size shrunk to just the unique prefix +@param[in] epsilon Epsilon value, vertices closer than this distance will be + melt together +@return Resulting index array +@m_deprecated_since_latest Use @ref removeDuplicatesInPlace() instead. -If you want to remove duplicate data from already indexed array, first remove -duplicates as if the array wasn't indexed at all and then use @ref duplicate() -to combine the two index arrays: +Similar to the above, except that it's operating on a @ref std::vector, which +gets shrunk as a result (instead of the prefix size being returned). This +variant is useful together with @ref combineIndexedArrays() to remove +duplicates in multiple incidental arrays --- first remove duplicates in each +array separately and then combine the resulting index arrays to single index +array, and reorder the data accordingly: -@snippet MagnumMeshTools.cpp removeDuplicates1 +@snippet MagnumMeshTools.cpp removeDuplicates-multiple +*/ +template CORRADE_DEPRECATED("use removeDuplicatesInPlace() instead") std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); +#endif -Removing duplicates in multiple indcidental arrays is also possible --- first -remove duplicates in each array separately and then use @ref combineIndexedArrays() -to combine the resulting index arrays to single index array and reorder the -data accordingly: +/** +@brief Remove duplicates from indexed floating-point vector data in-place +@param[in,out] indices Index array, which will get remapped to list just + unique vertices +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@param[in] epsilon Epsilon value, vertices closer than this distance will + be melt together +@return Size of unique prefix in the cleaned up @p data array +@m_since_latest -@snippet MagnumMeshTools.cpp removeDuplicates2 +Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView1D&, typename Vector::Type) +this variant is more suited for data that is already indexed as it works on +the existing index array instead of allocating a new one. */ -template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { +template std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { + /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is + int(-1) instead of the type I want */ + CORRADE_ASSERT(data.size() <= IndexType(-1), "MeshTools::removeDuplicatesIndexedInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << data.size() << "vertices", {}); + /* Get bounds. When NaNs appear, those will get collapsed together when you're lucky, or cause the whole data to disappear when you're not -- it needs a much more specialized handling to be robust. */ - std::pair minmax = Math::minmax(data); + std::pair minmax = Math::minmax(data); /* Make epsilon so large that std::size_t can index all vectors inside the bounds. */ epsilon = Math::max(epsilon, typename Vector::Type((minmax.second-minmax.first).max()/~std::size_t{})); - /* Resulting index array */ - std::vector resultIndices(data.size()); - std::iota(resultIndices.begin(), resultIndices.end(), 0); - /* Table containing original vector index for each discretized vector. Reserving more buckets than necessary (i.e. as if each vector was unique). */ - std::unordered_map, UnsignedInt, Implementation::VectorHash> table(data.size()); + std::size_t dataSize = data.size(); + std::unordered_map, UnsignedInt, Implementation::VectorHash> table(dataSize); - /* Index array for each pass, new data array */ - std::vector indices; - indices.reserve(data.size()); + /* Index array that'll be filled in each pass and then used for remapping + the `indices` */ + Containers::Array remapping{Containers::NoInit, dataSize}; /* First go with original coordinates, then move them by epsilon/2 in each direction. */ Vector moved; for(std::size_t moving = 0; moving <= Vector::Size; ++moving) { /* Go through all vectors */ - for(std::size_t i = 0; i != data.size(); ++i) { - /* Try to insert new vertex to the table */ - const Math::Vector v((data[i] + moved - minmax.first)/epsilon); + for(std::size_t i = 0; i != dataSize; ++i) { + /* Try to insert new vertex into the table */ + const Math::Vector v{(data[i] + moved - minmax.first)/epsilon}; const auto result = table.emplace(v, table.size()); - /* Add the (either new or already existing) index to index array */ - indices.push_back(result.first->second); + /* Add the (either new or already existing) index into the array */ + remapping[i] = result.first->second; - /* If this is new combination, copy the data to new (earlier) - possition in the array */ - if(result.second && i != table.size()-1) data[table.size()-1] = data[i]; + /* If this is a new combination, copy the data to new (earlier) + position in the array. Data in [table.size()-1, i) are already + present in the [0, table.size()-1) range from previous + iterations so we aren't overwriting anything. */ + if(result.second && i != table.size() - 1) + data[table.size() - 1] = data[i]; } - /* Shrink the data array */ - CORRADE_INTERNAL_ASSERT(data.size() >= table.size()); - data.resize(table.size()); - /* Remap the resulting index array */ - for(auto& i: resultIndices) i = indices[i]; + for(auto& i: indices) i = remapping[i]; /* Finished */ if(moving == Vector::Size) continue; - /* Move vertex coordinates by epsilon/2 in next direction */ + /* Move vertex coordinates by epsilon/2 in the next direction */ moved = Vector(); moved[moving] = epsilon/2; - /* Clear the structures for next pass */ + /* Next time go only through the unique prefix; clear the table for the + next pass */ + dataSize = table.size(); table.clear(); - indices.clear(); } - return resultIndices; + CORRADE_INTERNAL_ASSERT(data.size() >= dataSize); + return dataSize; } +template std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon) { + /* A trivial index array that'll be remapped and returned after */ + Containers::Array indices{Containers::NoInit, data.size()}; + std::iota(indices.begin(), indices.end(), 0); + const std::size_t size = removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), data, epsilon); + return {std::move(indices), size}; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon) { + /* A trivial index array that'll be remapped and returned after */ + std::vector indices(data.size()); + std::iota(indices.begin(), indices.end(), 0); + const std::size_t size = removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(data), epsilon); + data.resize(size); + return indices; +} +#endif + }} #endif diff --git a/src/Magnum/MeshTools/Subdivide.h b/src/Magnum/MeshTools/Subdivide.h index 41b7e42d86..149944f134 100644 --- a/src/Magnum/MeshTools/Subdivide.h +++ b/src/Magnum/MeshTools/Subdivide.h @@ -29,55 +29,95 @@ * @brief Function @ref Magnum::MeshTools::subdivide(), @ref Magnum::MeshTools::subdivideInPlace() */ -#include +#include #include +#include #include +#include "Magnum/Magnum.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#endif + namespace Magnum { namespace MeshTools { #ifndef DOXYGEN_GENERATING_OUTPUT -template void subdivideInPlace(Containers::ArrayView indices, Containers::ArrayView vertices, Interpolator interpolator); +template void subdivideInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& vertices, Interpolator interpolator); #endif /** -@brief Subdivide the mesh +@brief Subdivide a mesh @tparam Vertex Vertex data type @tparam Interpolator See the @p interpolator function parameter @param[in,out] indices Index array to operate on @param[in,out] vertices Vertex array to operate on @param interpolator Functor or function pointer which interpolates two adjacent vertices: @cpp Vertex interpolator(Vertex a, Vertex b) @ce +@m_since_latest + +Goes through all triangle faces and subdivides them into four new, enlarging +the @p indices and @p vertices arrays as appropriate. Removing duplicate +vertices in the mesh is up to the user. +@see @ref subdivideInPlace(), @ref removeDuplicatesInPlace() +*/ +template void subdivide(Containers::Array& indices, Containers::Array& vertices, Interpolator interpolator) { + CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3", ); + + arrayResize(vertices, Containers::NoInit, vertices.size() + indices.size()); + arrayResize(indices, Containers::NoInit, indices.size()*4); + subdivideInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(vertices), interpolator); +} -Goes through all triangle faces and subdivides them into four new. Removing -duplicate vertices in the mesh is up to the user. -@see @ref subdivideInPlace() +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief Subdivide a mesh +@m_deprecated_since_latest Use @ref subdivide(Containers::Array&, Containers::Array&vertices, Interpolator) + or @ref subdivideInPlace() instead. */ -template void subdivide(std::vector& indices, std::vector& vertices, Interpolator interpolator) { - CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3!", ); +template CORRADE_DEPRECATED("use subdivide(Containers::Array&, Containers::Array&vertices, Interpolator) or subdivideInPlace() instead") void subdivide(std::vector& indices, std::vector& vertices, Interpolator interpolator) { + CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3", ); vertices.resize(vertices.size() + indices.size()); indices.resize(indices.size()*4); - subdivideInPlace(Containers::arrayView(indices), Containers::arrayView(vertices), interpolator); + subdivideInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(vertices), interpolator); } +#endif /** -@brief Subdivide the mesh in-place +@brief Subdivide a mesh in-place @tparam Vertex Vertex data type @tparam Interpolator See the @p interpolator function parameter @param[in,out] indices Index array to operate on @param[in,out] vertices Vertex array to operate on @param interpolator Functor or function pointer which interpolates two adjacent vertices: @cpp Vertex interpolator(Vertex a, Vertex b) @ce - -Assumes the original triangle mesh indices are in the first quarter of the -@p indices array and original vertices in a prefix of the @p vertices array, -with @cpp indices.size() @ce left at the end of it for newly added vertices. -Then goes through all those triangle faces and subdivides them into four new, -filling up the remaining space in both. Removing duplicate vertices in the mesh -is up to the user. +@m_since_latest + +Assuming the original mesh has @f$ i @f$ indices and @f$ v @f$ vertices, +expects the @p indices array to have a size of @f$ 4i @f$ (as every triangle +face would be divided into four new), with the original indices being in the +first quarter, and the @p vertices array to have a size of @f$ v + i @f$ (as +every original triangle face will get three new vertices). Removing duplicate +vertices in the mesh is up to the user. + +Generally, for @f$ k @f$ subsequent subdivisions, the resulting index and +vertex array sizes @f$ i' @f$ and @f$ v' @f$ will be as following. To subdivide +the mesh multiple times in-place, pass correctly sized prefix of the arrays to +each step. @f[ + \begin{array}{rcl} + i' & = & 4^k i \\ + v' & = & v + \frac{1}{3}(i' - i) + \end{array} +@f] + +@see @ref subdivide(), @ref removeDuplicatesInPlace() */ -template void subdivideInPlace(Containers::ArrayView indices, Containers::ArrayView vertices, Interpolator interpolator) { - CORRADE_ASSERT(!(indices.size()%12), "MeshTools::subdivideInto(): can't divide" << indices.size() << "indices to four parts with each having triangle faces", ); +template void subdivideInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& vertices, Interpolator interpolator) { + CORRADE_ASSERT(!(indices.size()%12), "MeshTools::subdivideInPlace(): can't divide" << indices.size() << "indices to four parts with each having triangle faces", ); + /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is + int(-1) instead of the type I want */ + CORRADE_ASSERT(vertices.size() <= IndexType(-1), "MeshTools::subdivideInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << vertices.size() << "vertices", ); /* Subdivide each face to four new */ const std::size_t indexCount = indices.size()/4; @@ -85,7 +125,7 @@ template void subdivideInPlace(Containers::Arr std::size_t vertexOffset = vertices.size() - indexCount; for(std::size_t i = 0; i != indexCount; i += 3) { /* Interpolate each side */ - UnsignedInt newVertices[3]; + IndexType newVertices[3]; for(int j = 0; j != 3; ++j) { newVertices[j] = vertexOffset; vertices[vertexOffset++] = interpolator(vertices[indices[i+j]], vertices[indices[i+(j+1)%3]]); @@ -120,6 +160,14 @@ template void subdivideInPlace(Containers::Arr } } +/** + * @overload + * @m_since_latest + */ +template void subdivideInPlace(const Containers::ArrayView& indices, const Containers::StridedArrayView1D& vertices, Interpolator interpolator) { + subdivideInPlace(Containers::stridedArrayView(indices), vertices, interpolator); +} + }} #endif diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 10e54e1144..8f1913b8bd 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -23,29 +23,32 @@ # DEALINGS IN THE SOFTWARE. # -corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib) +corrade_add_test(MeshToolsCombineTest CombineTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib) -corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES Magnum) +corrade_add_test(MeshToolsConcatenateTest ConcatenateTest.cpp LIBRARIES MagnumMeshToolsTestLib) +corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives) -corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES Magnum) -corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES Magnum) +corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES MagnumMeshToolsTestLib) +corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp LIBRARIES Magnum) corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) corrade_add_test(MeshToolsTransformTest TransformTest.cpp LIBRARIES MagnumMeshTools) -corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBenchmark.cpp LIBRARIES MagnumPrimitives) +corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBenchmark.cpp LIBRARIES MagnumMeshTools MagnumPrimitives) # Graceful assert for testing set_property(TARGET - MeshToolsCombineIndexedArraysTest + MeshToolsConcatenateTest MeshToolsDuplicateTest MeshToolsInterleaveTest + MeshToolsRemoveDuplicatesTest MeshToolsSubdivideTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( - MeshToolsCombineIndexedArraysTest + MeshToolsCombineTest MeshToolsCompressIndicesTest + MeshToolsConcatenateTest MeshToolsDuplicateTest MeshToolsFlipNormalsTest MeshToolsGenerateNormalsTest @@ -57,6 +60,16 @@ set_target_properties( MeshToolsSubdivideRemov___Benchmark PROPERTIES FOLDER "Magnum/MeshTools/Test") +if(BUILD_DEPRECATED) + corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib) + set_property(TARGET + MeshToolsCombineIndexedArraysTest + APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") + set_target_properties( + MeshToolsCombineIndexedArraysTest + PROPERTIES FOLDER "Magnum/MeshTools/Test") +endif() + if(BUILD_GL_TESTS) # Otherwise CMake complains that Corrade::PluginManager is not found find_package(Corrade REQUIRED PluginManager) @@ -99,7 +112,7 @@ if(BUILD_GL_TESTS) MagnumDebugTools MagnumGL MagnumOpenGLTester - MagnumMeshTools + MagnumMeshToolsTestLib MagnumShaders FILES CompileTestFiles/color2D.tga diff --git a/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp b/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp index 7bd33f0d01..24fe08f98f 100644 --- a/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp +++ b/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp @@ -28,6 +28,8 @@ #include #include +#define _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS + #include "Magnum/Magnum.h" #include "Magnum/MeshTools/CombineIndexedArrays.h" @@ -47,6 +49,7 @@ CombineIndexedArraysTest::CombineIndexedArraysTest() { &CombineIndexedArraysTest::indexedArrays}); } +CORRADE_IGNORE_DEPRECATED_PUSH void CombineIndexedArraysTest::wrongIndexCount() { std::stringstream ss; Error redirectError{&ss}; @@ -87,6 +90,7 @@ void CombineIndexedArraysTest::indexedArrays() { CORRADE_COMPARE(array2, (std::vector{3, 4})); CORRADE_COMPARE(array3, (std::vector{6, 7})); } +CORRADE_IGNORE_DEPRECATED_POP }}}} diff --git a/src/Magnum/MeshTools/Test/CombineTest.cpp b/src/Magnum/MeshTools/Test/CombineTest.cpp new file mode 100644 index 0000000000..3d6b8f0e6b --- /dev/null +++ b/src/Magnum/MeshTools/Test/CombineTest.cpp @@ -0,0 +1,390 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include +#include +#include +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/MeshTools/Combine.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { namespace Test { namespace { + +struct CombineTest: TestSuite::Tester { + explicit CombineTest(); + + void combineIndexedAttributes(); + void combineIndexedAttributesIndicesOnly(); + + void combineIndexedAttributesNoMeshes(); + void combineIndexedAttributesNotIndexed(); + void combineIndexedAttributesDifferentPrimitive(); + void combineIndexedAttributesDifferentIndexCount(); + + void combineFaceAttributes(); + void combineFaceAttributesMeshNotIndexed(); + void combineFaceAttributesUnexpectedPrimitive(); + void combineFaceAttributesUnexpectedFaceCount(); + void combineFaceAttributesFacesNotInterleaved(); +}; + +constexpr struct { + const char* name; + bool indexed; +} CombineFaceAttributesData[] { + {"", false}, + {"indexed faces", true} +}; + +CombineTest::CombineTest() { + addTests({&CombineTest::combineIndexedAttributes, + &CombineTest::combineIndexedAttributesIndicesOnly, + + &CombineTest::combineIndexedAttributesNoMeshes, + &CombineTest::combineIndexedAttributesNotIndexed, + &CombineTest::combineIndexedAttributesDifferentPrimitive, + &CombineTest::combineIndexedAttributesDifferentIndexCount}); + + addInstancedTests({&CombineTest::combineFaceAttributes}, + Containers::arraySize(CombineFaceAttributesData)); + + addTests({&CombineTest::combineFaceAttributesMeshNotIndexed, + &CombineTest::combineFaceAttributesUnexpectedPrimitive, + &CombineTest::combineFaceAttributesUnexpectedFaceCount, + &CombineTest::combineFaceAttributesFacesNotInterleaved}); +} + +void CombineTest::combineIndexedAttributes() { + const UnsignedInt indicesA[]{2, 1, 2, 0}; + const Int dataA[]{2, 1, 0}; + const UnsignedByte indicesB[]{3, 4, 3, 2}; + const Short dataB[]{4, 3, 2, 1, 0}; + const UnsignedShort indicesC[]{7, 6, 7, 5}; + const Float dataC[]{0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; + Trade::MeshData a{MeshPrimitive::LineLoop, + {}, indicesA, Trade::MeshIndexData{indicesA}, + {}, dataA, {Trade::MeshAttributeData{ + Trade::meshAttributeCustom(2), Containers::arrayView(dataA)}}}; + Trade::MeshData b{MeshPrimitive::LineLoop, + {}, indicesB, Trade::MeshIndexData{indicesB}, + {}, dataB, {Trade::MeshAttributeData{ + Trade::meshAttributeCustom(17), Containers::arrayView(dataB)}}}; + Trade::MeshData c{MeshPrimitive::LineLoop, + {}, indicesC, Trade::MeshIndexData{indicesC}, + {}, dataC, {Trade::MeshAttributeData{ + Trade::meshAttributeCustom(22), Containers::arrayView(dataC)}}}; + + Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); + CORRADE_VERIFY(result.isIndexed()); + CORRADE_COMPARE(result.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(result.indices(), + Containers::arrayView({0, 1, 0, 2}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(result.attributeCount(), 3); + CORRADE_COMPARE(result.attributeName(0), Trade::meshAttributeCustom(2)); + CORRADE_COMPARE(result.attributeFormat(0), VertexFormat::Int); + CORRADE_COMPARE_AS(result.attribute(0), + Containers::arrayView({0, 1, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(result.attributeName(1), Trade::meshAttributeCustom(17)); + CORRADE_COMPARE(result.attributeFormat(1), VertexFormat::Short); + CORRADE_COMPARE_AS(result.attribute(1), + Containers::arrayView({1, 0, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(result.attributeName(2), Trade::meshAttributeCustom(22)); + CORRADE_COMPARE(result.attributeFormat(2), VertexFormat::Float); + CORRADE_COMPARE_AS(result.attribute(2), + Containers::arrayView({7.0f, 6.0f, 5.0f}), + TestSuite::Compare::Container); +} + +void CombineTest::combineIndexedAttributesIndicesOnly() { + const UnsignedInt indicesA[]{2, 1, 2}; + const UnsignedShort indicesB[]{3, 4, 3}; + const UnsignedByte indicesC[]{7, 6, 7}; + Trade::MeshData a{MeshPrimitive::LineLoop, {}, indicesA, + Trade::MeshIndexData{indicesA}, 3}; + Trade::MeshData b{MeshPrimitive::LineLoop, {}, indicesB, + Trade::MeshIndexData{indicesB}, 5}; + Trade::MeshData c{MeshPrimitive::LineLoop, {}, indicesC, + Trade::MeshIndexData{indicesC}, 8}; + + Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); + CORRADE_VERIFY(result.isIndexed()); + CORRADE_COMPARE(result.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(result.indices(), + Containers::arrayView({0, 1, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE(result.attributeCount(), 0); + CORRADE_COMPARE(result.vertexCount(), 2); +} + +void CombineTest::combineIndexedAttributesNoMeshes() { + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): no meshes passed\n"); +} + +void CombineTest::combineIndexedAttributesNotIndexed() { + const UnsignedShort indices[5]{}; + Trade::MeshData a{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + Trade::MeshData b{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + Trade::MeshData c{MeshPrimitive::Lines, 5}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 is not indexed\n"); +} + +void CombineTest::combineIndexedAttributesDifferentPrimitive() { + const UnsignedShort indices[5]{}; + Trade::MeshData a{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + Trade::MeshData b{MeshPrimitive::Points, + {}, indices, Trade::MeshIndexData{indices}, 1}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({a, b}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 1 is MeshPrimitive::Points but expected MeshPrimitive::Lines\n"); +} + +void CombineTest::combineIndexedAttributesDifferentIndexCount() { + const UnsignedShort indices[5]{}; + Trade::MeshData a{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + Trade::MeshData b{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + Trade::MeshData c{MeshPrimitive::Lines, + {}, indices, + Trade::MeshIndexData{Containers::arrayView(indices).prefix(4)}, 1}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 has 4 indices but expected 5\n"); +} + +void CombineTest::combineFaceAttributes() { + auto&& data = CombineFaceAttributesData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + using namespace Math::Literals; + + /* + 9 ------- 8 + 5 ------- 4 \ / 6 + \ / \ \ C / / \ + \ C / \ \ / / \ + \ / B \ \ / / B \ + \ / \ 7 / \ + 1 ------- 3 ==> 3 ------- 5 + / \ / 2 \ / + / \ B / / \ \ B / + / A \ / / \ \ / + / \ / / A \ \ / + 0 ------- 2 / \ 4 + 0 ------- 1 + */ + const UnsignedShort indices[]{ + 0, 2, 1, + 1, 2, 3, + 1, 3, 4, + 1, 4, 5 + }; + const Vector2 positions[] { + {0.0f, 0.0f}, + {0.5f, 1.0f}, + {1.0f, 0.0f}, + {1.5f, 1.0f}, + {1.0f, 2.0f}, + {0.0f, 2.0f} + }; + + const struct FaceData { + Color3 color; + Byte id; + } faceData[] { + {0xaaaaaa_rgbf, 'A'}, + {0xbbbbbb_rgbf, 'B'}, + {0xbbbbbb_rgbf, 'B'}, + {0xcccccc_rgbf, 'C'} + }; + + const UnsignedByte faceIndices[] { 0, 1, 1, 2 }; + const FaceData faceDataIndexed[] { + {0xaaaaaa_rgbf, 'A'}, + {0xbbbbbb_rgbf, 'B'}, + {0xcccccc_rgbf, 'C'} + }; + + const Trade::MeshData mesh{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positions)} + }}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, + {}, faceData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{faceData, + &faceData[0].color, 4, sizeof(FaceData)}}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), + Containers::StridedArrayView1D{faceData, + &faceData[0].id, 4, sizeof(FaceData)}}, + }}; + const Trade::MeshData faceAttributesIndexed{MeshPrimitive::Faces, + {}, faceIndices, Trade::MeshIndexData{faceIndices}, + {}, faceDataIndexed, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{faceDataIndexed, + &faceDataIndexed[0].color, 3, sizeof(FaceData)}}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), + Containers::StridedArrayView1D{faceDataIndexed, + &faceDataIndexed[0].id, 3, sizeof(FaceData)}}, + }}; + + Trade::MeshData combined = data.indexed ? + MeshTools::combineFaceAttributes(mesh, faceAttributesIndexed) : + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(combined.attributeCount(), 3); + CORRADE_COMPARE(combined.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(combined.indices(), + Containers::arrayView({ + 0, 1, 2, + 3, 4, 5, + 3, 5, 6, + 7, 8, 9 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(combined.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {0.5f, 1.0f}, + {0.5f, 1.0f}, + {1.0f, 0.0f}, + {1.5f, 1.0f}, + {1.0f, 2.0f}, + {0.5f, 1.0f}, + {1.0f, 2.0f}, + {0.0f, 2.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(combined.attribute(Trade::MeshAttribute::Color), + Containers::arrayView({ + 0xaaaaaa_rgbf, 0xaaaaaa_rgbf, 0xaaaaaa_rgbf, + 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, + 0xcccccc_rgbf, 0xcccccc_rgbf, 0xcccccc_rgbf + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(combined.attribute(Trade::meshAttributeCustom(25)), + Containers::arrayView({ + 'A', 'A', 'A', + 'B', 'B', 'B', 'B', + 'C', 'C', 'C' + }), TestSuite::Compare::Container); +} + +void CombineTest::combineFaceAttributesMeshNotIndexed() { + const Trade::MeshData mesh{MeshPrimitive::Triangles, 3}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): vertex mesh is not indexed\n"); +} + +void CombineTest::combineFaceAttributesUnexpectedPrimitive() { + const UnsignedInt indices[] { 0, 0, 0 }; + const Trade::MeshData a{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const Trade::MeshData b{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const Trade::MeshData faceA{MeshPrimitive::Instances, 0}; + const Trade::MeshData faceB{MeshPrimitive::Faces, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(a, faceA); + MeshTools::combineFaceAttributes(b, faceB); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got MeshPrimitive::Triangles and MeshPrimitive::Instances\n" + "MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got MeshPrimitive::Lines and MeshPrimitive::Faces\n"); +} + +void CombineTest::combineFaceAttributesUnexpectedFaceCount() { + const UnsignedInt indices[] { 0, 0, 0 }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, 2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): expected 1 face entries for 3 indices but got 2\n"); +} + +void CombineTest::combineFaceAttributesFacesNotInterleaved() { + using namespace Math::Literals; + + const UnsignedInt indices[] { 0, 0, 0, 0, 0, 0 }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const struct { + Color3 color[2]; + Byte id[2]; + } faceData[]{{ + {0xaaaaaa_rgbf, 0xbbbbbb_rgbf}, + {'A', 'B'} + }}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, + {}, faceData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::arrayView(faceData[0].color)}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), + Containers::arrayView(faceData[0].id)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): face attributes are not interleaved\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CombineTest) diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index d54cebb60d..b68344f704 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -23,9 +23,12 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -47,8 +50,14 @@ #include "Magnum/Shaders/Phong.h" #include "Magnum/Shaders/VertexColor.h" #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#endif #include "configure.h" @@ -78,8 +87,20 @@ struct CompileGLTest: GL::OpenGLTester { public: explicit CompileGLTest(); - void twoDimensions(); - void threeDimensions(); + /** @todo remove the template once MeshDataXD is gone */ + template void twoDimensions(); + template void threeDimensions(); + + void packedAttributes(); + + void customAttribute(); + void implementationSpecificAttributeFormat(); + void generateNormalsNoPosition(); + void generateNormals2DPosition(); + void generateNormalsNoFloats(); + + void externalBuffers(); + void externalBuffersInvalid(); private: PluginManager::Manager _manager{"nonexistent"}; @@ -138,6 +159,25 @@ constexpr struct { {"positions, nonindexed + gen smooth normals", Flag::NonIndexed|Flag::GeneratedSmoothNormals}, }; +constexpr struct { + const char* name; + bool indexed, moveIndices, moveVertices; +} DataExternal[] { + {"indexed", true, false, false}, + {"", false, false, false}, + {"move indices", true, true, false}, + {"move vertices", false, false, true}, + {"move both", true, true, true} +}; + +constexpr struct { + const char* name; + CompileFlags flags; +} CustomAttributeWarningData[] { + {"", {}}, + {"no warning", CompileFlag::NoWarnOnCustomAttributes} +}; + using namespace Math::Literals; constexpr Color4ub ImageData[] { @@ -148,11 +188,43 @@ constexpr Color4ub ImageData[] { }; CompileGLTest::CompileGLTest() { - addInstancedTests({&CompileGLTest::twoDimensions}, - Containers::arraySize(Data2D)); + addInstancedTests({ + &CompileGLTest::twoDimensions}, Containers::arraySize(Data2D)); + + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + addInstancedTests({ + &CompileGLTest::twoDimensions}, + Containers::arraySize(Data2D)); + CORRADE_IGNORE_DEPRECATED_POP + #endif - addInstancedTests({&CompileGLTest::threeDimensions}, - Containers::arraySize(Data3D)); + addInstancedTests({ + &CompileGLTest::threeDimensions}, + Containers::arraySize(Data3D)); + + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + addInstancedTests({ + &CompileGLTest::threeDimensions}, + Containers::arraySize(Data3D)); + CORRADE_IGNORE_DEPRECATED_POP + #endif + + addTests({&CompileGLTest::packedAttributes}); + + addInstancedTests({&CompileGLTest::customAttribute, + &CompileGLTest::implementationSpecificAttributeFormat}, + Containers::arraySize(CustomAttributeWarningData)); + + addTests({&CompileGLTest::generateNormalsNoPosition, + &CompileGLTest::generateNormals2DPosition, + &CompileGLTest::generateNormalsNoFloats}); + + addInstancedTests({&CompileGLTest::externalBuffers}, + Containers::arraySize(DataExternal)); + + addTests({&CompileGLTest::externalBuffersInvalid}); /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ @@ -187,7 +259,23 @@ CompileGLTest::CompileGLTest() { .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {4, 4}, ImageData}); } -void CompileGLTest::twoDimensions() { +template struct MeshTypeName; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData"; } +}; +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData2D"; } +}; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData3D"; } +}; +CORRADE_IGNORE_DEPRECATED_POP +#endif + +template void CompileGLTest::twoDimensions() { + setTestCaseTemplateName(MeshTypeName::name()); auto&& data = Data2D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -202,69 +290,63 @@ void CompileGLTest::twoDimensions() { |/ |/ | 0-----1-----2 */ - std::vector positions{ - {-0.75f, -0.75f}, - { 0.00f, -0.75f}, - { 0.75f, -0.75f}, - - {-0.75f, 0.0f}, - { 0.00f, 0.0f}, - { 0.75f, 0.0f}, - - {-0.75f, 0.75f}, - { 0.0f, 0.75f}, - { 0.75f, 0.75f} + const struct Vertex { + Vector2 position; + Vector2 textureCoordinates; + Color3 color; + } vertexData[]{ + {{-0.75f, -0.75f}, {0.0f, 0.0f}, 0x00ff00_rgbf}, + {{ 0.00f, -0.75f}, {0.5f, 0.0f}, 0x808000_rgbf}, + {{ 0.75f, -0.75f}, {1.0f, 0.0f}, 0xff0000_rgbf}, + + {{-0.75f, 0.00f}, {0.0f, 0.5f}, 0x00ff80_rgbf}, + {{ 0.00f, 0.00f}, {0.5f, 0.5f}, 0x808080_rgbf}, + {{ 0.75f, 0.00f}, {1.0f, 0.5f}, 0xff0080_rgbf}, + + {{-0.75f, 0.75f}, {0.0f, 1.0f}, 0x00ffff_rgbf}, + {{ 0.0f, 0.75f}, {0.5f, 1.0f}, 0x8080ff_rgbf}, + {{ 0.75f, 0.75f}, {1.0f, 1.0f}, 0xff00ff_rgbf} }; - std::vector> textureCoordinates2D; - if(data.flags & Flag::TextureCoordinates2D) textureCoordinates2D.push_back(std::vector{ - {0.0f, 0.0f}, - {0.5f, 0.0f}, - {1.0f, 0.0f}, - - {0.0f, 0.5f}, - {0.5f, 0.5f}, - {1.0f, 0.5f}, - - {0.0f, 1.0f}, - {0.5f, 1.0f}, - {1.0f, 1.0f} - }); - - std::vector> colors; - if(data.flags & Flag::Colors) colors.push_back(std::vector { - 0x00ff00_rgbf, - 0x808000_rgbf, - 0xff0000_rgbf, - - 0x00ff80_rgbf, - 0x808080_rgbf, - 0xff0080_rgbf, - - 0x00ffff_rgbf, - 0x8080ff_rgbf, - 0xff00ff_rgbf - }); - - std::vector indices{ + Containers::Array attributeData; + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexData, &vertexData[0].position, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::TextureCoordinates2D) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexData, &vertexData[0].textureCoordinates, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::Colors) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertexData, &vertexData[0].color, + Containers::arraySize(vertexData), sizeof(Vertex))}); + + const UnsignedInt indexData[]{ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7 }; - /* Duplicate positions if data are non-indexed. Testing only positions - alone ATM, don't bother with other attribs. */ - if(data.flags & Flag::NonIndexed) { - CORRADE_INTERNAL_ASSERT(textureCoordinates2D.empty()); - CORRADE_INTERNAL_ASSERT(colors.empty()); - positions = duplicate(indices, positions); - indices.clear(); - } + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, std::move(attributeData)}; + + /* Duplicate everything if data is non-indexed */ + if(data.flags & Flag::NonIndexed) meshData = duplicate(meshData); MAGNUM_VERIFY_NO_GL_ERROR(); - GL::Mesh mesh = compile(Trade::MeshData2D{MeshPrimitive::Triangles, indices, {positions}, textureCoordinates2D, colors}); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH /** @todo remove once MeshDataXD is gone */ + #endif + GL::Mesh mesh = compile(T{std::move(meshData)}); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_VERIFY_NO_GL_ERROR(); @@ -312,7 +394,8 @@ void CompileGLTest::twoDimensions() { } } -void CompileGLTest::threeDimensions() { +template void CompileGLTest::threeDimensions() { + setTestCaseTemplateName(MeshTypeName::name()); auto&& data = Data3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -327,96 +410,83 @@ void CompileGLTest::threeDimensions() { |/ |/ | 0-----1-----2 */ - std::vector positions{ - {-0.75f, -0.75f, -0.35f}, - { 0.00f, -0.75f, -0.25f}, - { 0.75f, -0.75f, -0.35f}, - - {-0.75f, 0.00f, -0.25f}, - { 0.00f, 0.00f, 0.00f}, - { 0.75f, 0.00f, -0.25f}, - - {-0.75f, 0.75f, -0.35f}, - { 0.0f, 0.75f, -0.25f}, - { 0.75f, 0.75f, -0.35f} + const struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinates; + Color4 color; + } vertexData[]{ + {{-0.75f, -0.75f, -0.35f}, Vector3{-0.5f, -0.5f, 1.0f}.normalized(), + {0.0f, 0.0f}, 0x00ff00_rgbf}, + {{ 0.00f, -0.75f, -0.25f}, Vector3{ 0.0f, -0.5f, 1.0f}.normalized(), + {0.5f, 0.0f}, 0x808000_rgbf}, + {{ 0.75f, -0.75f, -0.35f}, Vector3{ 0.5f, -0.5f, 1.0f}.normalized(), + {1.0f, 0.0f}, 0xff0000_rgbf}, + + {{-0.75f, 0.00f, -0.25f}, Vector3{-0.5f, 0.0f, 1.0f}.normalized(), + {0.0f, 0.5f}, 0x00ff80_rgbf}, + {{ 0.00f, 0.00f, 0.00f}, Vector3{ 0.0f, 0.0f, 1.0f}.normalized(), + {0.5f, 0.5f}, 0x808080_rgbf}, + {{ 0.75f, 0.00f, -0.25f}, Vector3{ 0.5f, 0.0f, 1.0f}.normalized(), + {1.0f, 0.5f}, 0xff0080_rgbf}, + + {{-0.75f, 0.75f, -0.35f}, Vector3{-0.5f, 0.5f, 1.0f}.normalized(), + {0.0f, 1.0f}, 0x00ffff_rgbf}, + {{ 0.0f, 0.75f, -0.25f}, Vector3{ 0.0f, 0.5f, 1.0f}.normalized(), + {0.5f, 1.0f}, 0x8080ff_rgbf}, + {{ 0.75f, 0.75f, -0.35f}, Vector3{ 0.5f, 0.5f, 1.0f}.normalized(), + {1.0f, 1.0f}, 0xff00ff_rgbf} }; - std::vector> normals; - if(data.flags & Flag::Normals) normals.push_back(std::vector{ - Vector3{-0.5f, -0.5f, 1.0f}.normalized(), - Vector3{ 0.0f, -0.5f, 1.0f}.normalized(), - Vector3{ 0.5f, -0.5f, 1.0f}.normalized(), - - Vector3{-0.5f, 0.0f, 1.0f}.normalized(), - Vector3{ 0.0f, 0.0f, 1.0f}.normalized(), - Vector3{ 0.5f, 0.0f, 1.0f}.normalized(), - - Vector3{-0.5f, 0.5f, 1.0f}.normalized(), - Vector3{ 0.0f, 0.5f, 1.0f}.normalized(), - Vector3{ 0.5f, 0.5f, 1.0f}.normalized(), - }); - - std::vector> textureCoordinates2D; - if(data.flags & Flag::TextureCoordinates2D) textureCoordinates2D.push_back(std::vector{ - {0.0f, 0.0f}, - {0.5f, 0.0f}, - {1.0f, 0.0f}, - - {0.0f, 0.5f}, - {0.5f, 0.5f}, - {1.0f, 0.5f}, - - {0.0f, 1.0f}, - {0.5f, 1.0f}, - {1.0f, 1.0f} - }); - - std::vector> colors; - if(data.flags & Flag::Colors) colors.push_back(std::vector { - 0x00ff00_rgbf, - 0x808000_rgbf, - 0xff0000_rgbf, - - 0x00ff80_rgbf, - 0x808080_rgbf, - 0xff0080_rgbf, - - 0x00ffff_rgbf, - 0x8080ff_rgbf, - 0xff00ff_rgbf - }); - - std::vector indices{ + Containers::Array attributeData; + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexData, &vertexData[0].position, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::Normals) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, + Containers::stridedArrayView(vertexData, &vertexData[0].normal, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::TextureCoordinates2D) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexData, &vertexData[0].textureCoordinates, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::Colors) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertexData, &vertexData[0].color, + Containers::arraySize(vertexData), sizeof(Vertex))}); + + const UnsignedByte indexData[]{ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7 }; - /* Duplicate everything if data are non-indexed */ - if(data.flags & Flag::NonIndexed) { - positions = duplicate(indices, positions); - - if(data.flags & Flag::Normals) - normals[0] = duplicate(indices, normals[0]); + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, std::move(attributeData)}; - if(data.flags & Flag::TextureCoordinates2D) - textureCoordinates2D[0] = duplicate(indices, textureCoordinates2D[0]); - - if(data.flags & Flag::Colors) - colors[0] = duplicate(indices, colors[0]); - - indices.clear(); - } + /* Duplicate everything if data is non-indexed */ + if(data.flags & Flag::NonIndexed) meshData = duplicate(meshData); MAGNUM_VERIFY_NO_GL_ERROR(); CompileFlags flags; if(data.flags & Flag::GeneratedFlatNormals) flags |= CompileFlag::GenerateFlatNormals; - else if(data.flags & Flag::GeneratedSmoothNormals) + if(data.flags & Flag::GeneratedSmoothNormals) flags |= CompileFlag::GenerateSmoothNormals; - GL::Mesh mesh = compile(Trade::MeshData3D{MeshPrimitive::Triangles, indices, {positions}, normals, textureCoordinates2D, colors}, flags); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH /** @todo remove once MeshDataXD is gone */ + #endif + GL::Mesh mesh = compile(T{std::move(meshData)}, flags); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_VERIFY_NO_GL_ERROR(); @@ -527,6 +597,304 @@ void CompileGLTest::threeDimensions() { } } +void CompileGLTest::packedAttributes() { + /* Same as above, just packed */ + const struct Vertex { + Vector3s position; + Vector3s normal; + Vector2us textureCoordinates; + Color4ub color; + } vertexData[]{ + {Math::pack(Vector3{-0.75f, -0.75f, -0.35f}), + Math::pack(Vector3{-0.5f, -0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.0f, 0.0f}), 0x00ff00_rgb}, + {Math::pack(Vector3{ 0.00f, -0.75f, -0.25f}), + Math::pack(Vector3{ 0.0f, -0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.5f, 0.0f}), 0x808000_rgb}, + {Math::pack(Vector3{ 0.75f, -0.75f, -0.35f}), + Math::pack(Vector3{ 0.5f, -0.5f, 1.0f}.normalized()), + Math::pack(Vector2{1.0f, 0.0f}), 0xff0000_rgb}, + + {Math::pack(Vector3{-0.75f, 0.00f, -0.25f}), + Math::pack(Vector3{-0.5f, 0.0f, 1.0f}.normalized()), + Math::pack(Vector2{0.0f, 0.5f}), 0x00ff80_rgb}, + {Math::pack(Vector3{ 0.00f, 0.00f, 0.00f}), + Math::pack(Vector3{ 0.0f, 0.0f, 1.0f}.normalized()), + Math::pack(Vector2{0.5f, 0.5f}), 0x808080_rgb}, + {Math::pack(Vector3{ 0.75f, 0.00f, -0.25f}), + Math::pack(Vector3{ 0.5f, 0.0f, 1.0f}.normalized()), + Math::pack(Vector2{1.0f, 0.5f}), 0xff0080_rgb}, + + {Math::pack(Vector3{-0.75f, 0.75f, -0.35f}), + Math::pack(Vector3{-0.5f, 0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.0f, 1.0f}), 0x00ffff_rgb}, + {Math::pack(Vector3{ 0.0f, 0.75f, -0.25f}), + Math::pack(Vector3{ 0.0f, 0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.5f, 1.0f}), 0x8080ff_rgb}, + {Math::pack(Vector3{ 0.75f, 0.75f, -0.35f}), + Math::pack(Vector3{ 0.5f, 0.5f, 1.0f}.normalized()), + Math::pack(Vector2{1.0f, 1.0f}), 0xff00ff_rgb} + }; + static_assert(sizeof(Vertex) % 4 == 0, + "the vertex is not 4-byte aligned and that's bad"); + + const UnsignedByte indexData[]{ + 0, 1, 4, 0, 4, 3, + 1, 2, 5, 1, 5, 4, + 3, 4, 7, 3, 7, 6, + 4, 5, 8, 4, 8, 7 + }; + + Trade::MeshData meshData{MeshPrimitive::Triangles, {}, indexData, + Trade::MeshIndexData{indexData}, {}, vertexData, { + Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, + VertexFormat::Vector3sNormalized, + Containers::stridedArrayView(vertexData, &vertexData[0].position, + Containers::arraySize(vertexData), sizeof(Vertex))}, + Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, + VertexFormat::Vector3sNormalized, + Containers::stridedArrayView(vertexData, &vertexData[0].normal, + Containers::arraySize(vertexData), sizeof(Vertex))}, + Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, + VertexFormat::Vector2usNormalized, + Containers::stridedArrayView(vertexData, &vertexData[0].textureCoordinates, + Containers::arraySize(vertexData), sizeof(Vertex))}, + Trade::MeshAttributeData{ + Trade::MeshAttribute::Color, + /* It should figure out the type itself here */ + Containers::stridedArrayView(vertexData, &vertexData[0].color, + Containers::arraySize(vertexData), sizeof(Vertex))} + }}; + + GL::Mesh mesh = compile(meshData); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Matrix4 projection = Matrix4::perspectiveProjection(45.0_degf, 1.0f, 0.1f, 10.0f); + Matrix4 transformation = Matrix4::translation(Vector3::zAxis(-2.0f)); + + /* In all checks below, the rendering should be practically 1:1 as above + with full-blown attribute types */ + + /* Check positions and normals */ + _framebuffer.clear(GL::FramebufferClear::Color); + _phong + .setDiffuseColor(0x33ff66_rgbf) + .setTransformationMatrix(transformation) + .setNormalMatrix(transformation.normalMatrix()) + .setProjectionMatrix(projection); + mesh.draw(_phong); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "phong.tga"), + /* SwiftShader has some minor off-by-one precision differences */ + (DebugTools::CompareImageToFile{_manager, 0.5f, 0.0113f})); + + /* Check colors */ + _framebuffer.clear(GL::FramebufferClear::Color); + _color3D + .setTransformationProjectionMatrix(projection*transformation); + mesh.draw(_color3D); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "color3D.tga"), + /* SwiftShader has some minor off-by-one precision differences */ + (DebugTools::CompareImageToFile{_manager, 0.5f, 0.0162f})); + + /* Check texture coordinates */ + _framebuffer.clear(GL::FramebufferClear::Color); + _flatTextured3D + .setTransformationProjectionMatrix(projection*transformation) + .bindTexture(_texture); + mesh.draw(_flatTextured3D); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "textured3D.tga"), + /* SwiftShader has some minor off-by-one precision differences */ + (DebugTools::CompareImageToFile{_manager, 1.0f, 0.0948f})); +} + +void CompileGLTest::customAttribute() { + auto&& instanceData = CustomAttributeWarningData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::meshAttributeCustom(115), + VertexFormat::Short, nullptr}}}; + + std::ostringstream out; + Warning redirectError{&out}; + if(instanceData.flags) + MeshTools::compile(data, instanceData.flags); + else + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), instanceData.flags ? "" : + "MeshTools::compile(): ignoring unknown attribute Trade::MeshAttribute::Custom(115)\n"); +} + +void CompileGLTest::implementationSpecificAttributeFormat() { + auto&& instanceData = CustomAttributeWarningData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, + vertexFormatWrap(0xdead), nullptr}}}; + + std::ostringstream out; + Warning redirectError{&out}; + if(instanceData.flags) + MeshTools::compile(data, instanceData.flags); + else + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), instanceData.flags ? "" : + "MeshTools::compile(): ignoring attribute Trade::MeshAttribute::Position with an implementation-specific format 0xdead\n"); +} + +void CompileGLTest::generateNormalsNoPosition() { + Trade::MeshData data{MeshPrimitive::Triangles, 1}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compile(data, CompileFlag::GenerateFlatNormals); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): the mesh has no positions, can't generate normals\n"); +} + +void CompileGLTest::generateNormals2DPosition() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compile(data, CompileFlag::GenerateFlatNormals); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): can't generate normals for VertexFormat::Vector2 positions\n"); +} + +void CompileGLTest::generateNormalsNoFloats() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3h, nullptr}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compile(data, CompileFlag::GenerateFlatNormals); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): can't generate normals into VertexFormat::Vector3h\n"); +} + +void CompileGLTest::externalBuffers() { + auto&& data = DataExternal[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* + 6-----7-----8 + | /| /| + | / | / | + |/ |/ | + 3-----4-----5 + | /| /| + | / | / | + |/ |/ | + 0-----1-----2 + */ + Vector2 positions[] { + {-0.75f, -0.75f}, + { 0.00f, -0.75f}, + { 0.75f, -0.75f}, + + {-0.75f, 0.00f}, + { 0.00f, 0.00f}, + { 0.75f, 0.00f}, + + {-0.75f, 0.75f}, + { 0.0f, 0.75f}, + { 0.75f, 0.75f} + }; + + const UnsignedShort indexData[]{ + 0, 1, 4, 0, 4, 3, + 1, 2, 5, 1, 5, 4, + 3, 4, 7, 3, 7, 6, + 4, 5, 8, 4, 8, 7 + }; + + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, positions, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}}}; + + /* Duplicate everything if data is non-indexed */ + if(!data.indexed) meshData = duplicate(meshData); + + GL::Buffer indices{NoCreate}; + if(meshData.isIndexed()) { + indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; + indices.setData(meshData.indexData()); + } + + GL::Buffer vertices{GL::Buffer::TargetHint::Array}; + vertices.setData(meshData.vertexData()); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + GL::Mesh mesh{NoCreate}; + if(data.moveIndices && data.moveVertices) + mesh = compile(meshData, std::move(indices), std::move(vertices)); + else if(data.moveIndices && !data.moveVertices) + mesh = compile(meshData, std::move(indices), vertices); + else if(!data.moveIndices && data.moveVertices) + mesh = compile(meshData, indices, std::move(vertices)); + else + mesh = compile(meshData, indices, vertices); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + _framebuffer.clear(GL::FramebufferClear::Color); + _flat2D.setColor(0xff3366_rgbf); + mesh.draw(_flat2D); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "flat2D.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +void CompileGLTest::externalBuffersInvalid() { + Trade::MeshData data{MeshPrimitive::Triangles, 5}; + Trade::MeshData indexedData{MeshPrimitive::Triangles, + nullptr, Trade::MeshIndexData{MeshIndexType::UnsignedInt, nullptr}, + {}}; + + std::ostringstream out; + Error redirectError{&out}; + compile(data, GL::Buffer{NoCreate}, GL::Buffer{}); /* this is okay */ + compile(data, GL::Buffer{NoCreate}, GL::Buffer{NoCreate}); + compile(indexedData, GL::Buffer{NoCreate}, GL::Buffer{}); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): invalid external buffer(s)\n" + "MeshTools::compile(): invalid external buffer(s)\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CompileGLTest) diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index 309271083e..476475f896 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -24,107 +24,319 @@ */ #include +#include #include +#include #include #include #include #include +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { struct CompressIndicesTest: TestSuite::Tester { explicit CompressIndicesTest(); - void compressChar(); - void compressShort(); - void compressInt(); + template void compressUnsignedByte(); + template void compressUnsignedShort(); + template void compressUnsignedInt(); + void compressUnsignedByteInflateToShort(); + void compressOffset(); + template void compressOffsetNegative(); + /* No compressErased(), as that's tested in the templates above */ + void compressErasedNonContiguous(); + void compressErasedWrongIndexSize(); + #ifdef MAGNUM_BUILD_DEPRECATED + void compressDeprecated(); + #endif + template void compressMeshData(); + void compressMeshDataMove(); + void compressMeshDataNonIndexed(); + + #ifdef MAGNUM_BUILD_DEPRECATED void compressAsShort(); + #endif }; CompressIndicesTest::CompressIndicesTest() { - addTests({&CompressIndicesTest::compressChar, - &CompressIndicesTest::compressShort, - &CompressIndicesTest::compressInt, + addTests({&CompressIndicesTest::compressUnsignedByte, + &CompressIndicesTest::compressUnsignedByte, + &CompressIndicesTest::compressUnsignedByte, + &CompressIndicesTest::compressUnsignedShort, + &CompressIndicesTest::compressUnsignedShort, + &CompressIndicesTest::compressUnsignedInt, + &CompressIndicesTest::compressUnsignedByteInflateToShort, + &CompressIndicesTest::compressOffset, + &CompressIndicesTest::compressOffsetNegative, + &CompressIndicesTest::compressOffsetNegative, + &CompressIndicesTest::compressOffsetNegative, + &CompressIndicesTest::compressErasedNonContiguous, + &CompressIndicesTest::compressErasedWrongIndexSize, + #ifdef MAGNUM_BUILD_DEPRECATED + &CompressIndicesTest::compressDeprecated, + #endif + + &CompressIndicesTest::compressMeshData, + &CompressIndicesTest::compressMeshData, + &CompressIndicesTest::compressMeshData, + &CompressIndicesTest::compressMeshDataMove, + &CompressIndicesTest::compressMeshDataNonIndexed, - &CompressIndicesTest::compressAsShort}); + #ifdef MAGNUM_BUILD_DEPRECATED + &CompressIndicesTest::compressAsShort + #endif + }); } -void CompressIndicesTest::compressChar() { - Containers::Array data; - MeshIndexType type; - UnsignedInt start, end; - std::tie(data, type, start, end) = MeshTools::compressIndices( - std::vector{1, 2, 3, 0, 4}); +template void CompressIndicesTest::compressUnsignedByte() { + setTestCaseTemplateName(Math::TypeTraits::name()); - CORRADE_COMPARE(start, 0); - CORRADE_COMPARE(end, 4); - CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x01, 0x02, 0x03, 0x00, 0x04 })); + const T indices[]{1, 2, 3, 0, 4}; + /* By default it has 16-byte type as minimum, override */ + std::pair, MeshIndexType> out = + compressIndices(indices, MeshIndexType::UnsignedByte); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedByte); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 2, 3, 0, 4}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), MeshIndexType::UnsignedByte); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedByte); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 2, 3, 0, 4}), + TestSuite::Compare::Container); +} + +template void CompressIndicesTest::compressUnsignedShort() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{1, 256, 0, 5}; + std::pair, MeshIndexType> out = compressIndices(indices); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices))); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); +} + +template void CompressIndicesTest::compressUnsignedInt() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{65536, 3, 2}; + std::pair, MeshIndexType> out = compressIndices(indices); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({65536, 3, 2}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices))); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({65536, 3, 2}), + TestSuite::Compare::Container); +} + +void CompressIndicesTest::compressUnsignedByteInflateToShort() { + const UnsignedByte indices[]{1, 2, 3, 0, 4}; + /* That's the default */ + std::pair, MeshIndexType> out = compressIndices(indices); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 2, 3, 0, 4}), + TestSuite::Compare::Container); +} + +void CompressIndicesTest::compressOffset() { + const UnsignedInt indices[]{75000 + 1, 75000 + 256, 75000 + 0, 75000 + 5}; + std::pair, MeshIndexType> out = compressIndices(indices, 75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), 75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); +} + +template void CompressIndicesTest::compressOffsetNegative() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{1, 255, 0, 5}; + std::pair, MeshIndexType> out = compressIndices(indices, -75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({75000 + 1, 75000 + 255, 75000 + 0, 75000 + 5}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), -75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({75000 + 1, 75000 + 255, 75000 + 0, 75000 + 5}), + TestSuite::Compare::Container); } -void CompressIndicesTest::compressShort() { +void CompressIndicesTest::compressErasedNonContiguous() { + const char indices[6*4]{}; + + std::stringstream out; + Error redirectError{&out}; + compressIndices(Containers::StridedArrayView2D{indices, {6, 2}, {4, 2}}); + CORRADE_COMPARE(out.str(), + "MeshTools::compressIndices(): second view dimension is not contiguous\n"); +} + +void CompressIndicesTest::compressErasedWrongIndexSize() { + const char indices[6*3]{}; + + std::stringstream out; + Error redirectError{&out}; + compressIndices(Containers::StridedArrayView2D{indices, {6, 3}}.every(2)); + CORRADE_COMPARE(out.str(), + "MeshTools::compressIndices(): expected index type size 1, 2 or 4 but got 3\n"); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void CompressIndicesTest::compressDeprecated() { Containers::Array data; MeshIndexType type; UnsignedInt start, end; + CORRADE_IGNORE_DEPRECATED_PUSH std::tie(data, type, start, end) = MeshTools::compressIndices( std::vector{1, 256, 0, 5}); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(start, 0); CORRADE_COMPARE(end, 256); CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); - if(!Utility::Endianness::isBigEndian()) { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x01, 0x00, - 0x00, 0x01, - 0x00, 0x00, - 0x05, 0x00 })); - } else { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x00, 0x01, - 0x01, 0x00, - 0x00, 0x00, - 0x00, 0x05 })); - } + CORRADE_COMPARE_AS(Containers::arrayCast(data), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); } +#endif -void CompressIndicesTest::compressInt() { - Containers::Array data; - MeshIndexType type; - UnsignedInt start, end; - std::tie(data, type, start, end) = MeshTools::compressIndices( - std::vector{65536, 3, 2}); - - CORRADE_COMPARE(start, 2); - CORRADE_COMPARE(end, 65536); - CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); - - if(!Utility::Endianness::isBigEndian()) { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x00, 0x00, 0x01, 0x00, - 0x03, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00 })); - } else { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x02 })); - } +template void CompressIndicesTest::compressMeshData() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + struct { + Vector2 positions[103]; + Vector3 normals[103]; + } vertexData{}; + vertexData.positions[100] = {1.3f, 0.3f}; + vertexData.positions[101] = {0.87f, 1.1f}; + vertexData.positions[102] = {1.0f, -0.5f}; + vertexData.normals[100] = Vector3::xAxis(); + vertexData.normals[101] = Vector3::yAxis(); + vertexData.normals[102] = Vector3::zAxis(); + + T indices[] = {102, 101, 100, 101, 102}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, + {}, Containers::arrayView(&vertexData, 1), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData.positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData.normals)} + }}; + CORRADE_COMPARE(data.vertexCount(), 103); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 103*sizeof(Vector2)); + + Trade::MeshData compressed = compressIndices(data); + CORRADE_COMPARE(compressed.indexCount(), 5); + CORRADE_COMPARE(compressed.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(compressed.indices(), + Containers::arrayView({2, 1, 0, 1, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(compressed.vertexCount(), 3); + CORRADE_COMPARE(compressed.attributeOffset(0), 100*sizeof(Vector2)); + CORRADE_COMPARE(compressed.attributeOffset(1), 103*sizeof(Vector2) + 100*sizeof(Vector3)); + CORRADE_COMPARE_AS(compressed.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(compressed.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}), + TestSuite::Compare::Container); +} + +void CompressIndicesTest::compressMeshDataMove() { + Containers::Array vertexData{103*24}; + Containers::StridedArrayView1D positionView{vertexData, + reinterpret_cast(vertexData.data()), 103, 8}; + Containers::StridedArrayView1D normalView{vertexData, + reinterpret_cast(vertexData.data() + 103*sizeof(Vector2)), 103, 12}; + UnsignedInt indices[] = {102, 101, 100, 101, 102}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, + std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView} + }}; + CORRADE_COMPARE(data.vertexCount(), 103); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 103*sizeof(Vector2)); + + Trade::MeshData compressed = compressIndices(std::move(data)); + CORRADE_COMPARE(compressed.indexCount(), 5); + CORRADE_COMPARE(compressed.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(compressed.indices(), + Containers::arrayView({2, 1, 0, 1, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(compressed.vertexCount(), 3); + CORRADE_COMPARE(compressed.attributeOffset(0), 100*sizeof(Vector2)); + CORRADE_COMPARE(compressed.attributeOffset(1), 103*sizeof(Vector2) + 100*sizeof(Vector3)); + /* The vertex data should be moved, not copied */ + CORRADE_VERIFY(compressed.vertexData().data() == positionView.data()); +} + +void CompressIndicesTest::compressMeshDataNonIndexed() { + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compressIndices(Trade::MeshData{MeshPrimitive::TriangleFan, 5}); + CORRADE_COMPARE(out.str(), + "MeshTools::compressIndices(): mesh data not indexed\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED void CompressIndicesTest::compressAsShort() { + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE_AS(MeshTools::compressIndicesAs({123, 456}), - (Containers::Array{Containers::InPlaceInit, {123, 456}}), + Containers::arrayView({123, 456}), TestSuite::Compare::Container); std::ostringstream out; Error redirectError{&out}; MeshTools::compressIndicesAs({65536}); CORRADE_COMPARE(out.str(), "MeshTools::compressIndicesAs(): type too small to represent value 65536\n"); + CORRADE_IGNORE_DEPRECATED_POP } +#endif }}}} diff --git a/src/Magnum/MeshTools/Test/ConcatenateTest.cpp b/src/Magnum/MeshTools/Test/ConcatenateTest.cpp new file mode 100644 index 0000000000..393a4ce857 --- /dev/null +++ b/src/Magnum/MeshTools/Test/ConcatenateTest.cpp @@ -0,0 +1,595 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include +#include +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/MeshTools/Concatenate.h" + +namespace Magnum { namespace MeshTools { namespace Test { namespace { + +struct ConcatenateTest: TestSuite::Tester { + explicit ConcatenateTest(); + + void concatenate(); + void concatenateNotIndexed(); + void concatenateNoAttributes(); + void concatenateNoAttributesNotIndexed(); + void concatenateOne(); + void concatenateOneRvalue(); + void concatenateInto(); + void concatenateIntoNoIndexArray(); + void concatenateIntoNonOwnedAttributeArray(); + + void concatenateUnsupportedPrimitive(); + void concatenateInconsistentPrimitive(); + void concatenateInconsistentAttributeType(); + void concatenateIntoNoMeshes(); +}; + +ConcatenateTest::ConcatenateTest() { + addTests({&ConcatenateTest::concatenate, + &ConcatenateTest::concatenateNotIndexed, + &ConcatenateTest::concatenateNoAttributes, + &ConcatenateTest::concatenateNoAttributesNotIndexed, + &ConcatenateTest::concatenateOne, + &ConcatenateTest::concatenateOneRvalue, + &ConcatenateTest::concatenateInto, + &ConcatenateTest::concatenateIntoNoIndexArray, + &ConcatenateTest::concatenateIntoNonOwnedAttributeArray, + + &ConcatenateTest::concatenateUnsupportedPrimitive, + &ConcatenateTest::concatenateInconsistentPrimitive, + &ConcatenateTest::concatenateInconsistentAttributeType, + &ConcatenateTest::concatenateIntoNoMeshes}); +} + +/* MSVC 2015 doesn't like unnamed bitfields in local structs, so thhis has to + be outside */ +struct VertexDataA { + Vector2 texcoords1; + Vector2 texcoords2; + Int:32; + Vector3 position; +}; + +void ConcatenateTest::concatenate() { + using namespace Math::Literals; + + /* First is non-indexed, this layout (including the gap) will be + preserved */ + const VertexDataA vertexDataA[]{ + {{0.1f, 0.2f}, {0.5f, 0.6f}, {1.0f, 2.0f, 3.0f}}, + {{0.3f, 0.4f}, {0.7f, 0.8f}, {4.0f, 5.0f, 6.0f}} + }; + Trade::MeshData a{MeshPrimitive::Points, {}, vertexDataA, { + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataA, + &vertexDataA[0].texcoords1, 2, sizeof(VertexDataA))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataA, + &vertexDataA[0].texcoords2, 2, sizeof(VertexDataA))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexDataA, + &vertexDataA[0].position, 2, sizeof(VertexDataA))}, + }}; + + /* Second is indexed, has only one texture coordinate of the two, an extra + color (which gets ignored) and misses the position (which will be + zero-filled) */ + const struct VertexDataB { + Color4 color; + Vector2 texcoords1; + } vertexDataB[]{ + {0x112233_rgbf, {0.15f, 0.25f}}, + {0x445566_rgbf, {0.35f, 0.45f}}, + {0x778899_rgbf, {0.55f, 0.65f}}, + {0xaabbcc_rgbf, {0.75f, 0.85f}} + }; + const UnsignedShort indicesB[]{0, 2, 1, 0, 3, 2}; + Trade::MeshData b{MeshPrimitive::Points, + {}, indicesB, Trade::MeshIndexData{indicesB}, {}, vertexDataB, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertexDataB, + &vertexDataB[0].color, 4, sizeof(VertexDataB))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataB, + &vertexDataB[0].texcoords1, 4, sizeof(VertexDataB))}, + }}; + + /* Third is again non-indexed, has one texcoord attribute more (which will + get ignored). Additionally, attribute memory order is inversed and mixed + together to verify the attributes are picked based on declaration order, + not memory order. */ + const struct VertexDataC { + Vector2 texcoords2; + Vector3 position; + Vector2 texcoords3; + Vector2 texcoords1; + } vertexDataC[]{ + {{0.425f, 0.475f}, {1.5f, 2.5f, 3.5f}, {0.725f, 0.775f}, {0.125f, 0.175f}}, + {{0.525f, 0.575f}, {4.5f, 5.5f, 6.5f}, {0.825f, 0.875f}, {0.225f, 0.275f}}, + {{0.625f, 0.675f}, {7.5f, 8.5f, 9.5f}, {0.925f, 0.975f}, {0.325f, 0.375f}}, + }; + Trade::MeshData c{MeshPrimitive::Points, {}, vertexDataC, { + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].texcoords1, 3, sizeof(VertexDataC))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].position, 3, sizeof(VertexDataC))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].texcoords2, 3, sizeof(VertexDataC))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].texcoords3, 3, sizeof(VertexDataC))}, + }}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 3); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {}, {}, {}, {}, /* Missing in the second mesh */ + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.1f, 0.2f}, + {0.3f, 0.4f}, + {0.15f, 0.25f}, + {0.35f, 0.45f}, + {0.55f, 0.65f}, + {0.75f, 0.85f}, + {0.125f, 0.175f}, + {0.225f, 0.275f}, + {0.325f, 0.375f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates, 1), + Containers::arrayView({ + {0.5f, 0.6f}, + {0.7f, 0.8f}, + {}, {}, {}, {}, /* Missing in the second mesh */ + {0.425f, 0.475f}, + {0.525f, 0.575f}, + {0.625f, 0.675f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 0, 1, /* implicit for the first nonindexed mesh */ + 2, 4, 3, 2, 5, 4, /* offset for the second indexed mesh */ + 6, 7, 8 /* implicit + offset for the third mesh */ + }), TestSuite::Compare::Container); + + /* The original interleaved layout should be preserved */ + CORRADE_VERIFY(isInterleaved(dst)); + CORRADE_COMPARE(dst.attributeStride(0), sizeof(VertexDataA)); + CORRADE_COMPARE(dst.attributeOffset(0), 0); + CORRADE_COMPARE(dst.attributeOffset(1), sizeof(Vector2)); + CORRADE_COMPARE(dst.attributeOffset(2), 2*sizeof(Vector2) + 4); +} + +void ConcatenateTest::concatenateNotIndexed() { + const Vector3 positionA[]{ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f} + }; + Trade::MeshData a{MeshPrimitive::Points, {}, positionA, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionA)} + }}; + + const Vector3 positionB[]{ + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f}, + }; + Trade::MeshData b{MeshPrimitive::Points, {}, positionB, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionB)} + }}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, b}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 1); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f}, + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(!dst.isIndexed()); +} + +void ConcatenateTest::concatenateNoAttributes() { + /* Compared to concatenate(), now the first and last is indexed */ + const UnsignedShort indicesA[]{1, 0}; + Trade::MeshData a{MeshPrimitive::Points, {}, indicesA, Trade::MeshIndexData{indicesA}, 2}; + + /* Second is not indexed, just a vertex count */ + Trade::MeshData b{MeshPrimitive::Points, 6}; + + const UnsignedByte indicesC[]{1, 0, 1, 0}; + Trade::MeshData c{MeshPrimitive::Points, {}, indicesC, Trade::MeshIndexData{indicesC}, 2}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 0); + CORRADE_COMPARE(dst.vertexCount(), 10); + CORRADE_VERIFY(!dst.vertexData()); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 1, 0, + 2, 3, 4, 5, 6, 7, + 9, 8, 9, 8 + }), TestSuite::Compare::Container); +} + +void ConcatenateTest::concatenateNoAttributesNotIndexed() { + Trade::MeshData a{MeshPrimitive::Points, 3}; + Trade::MeshData b{MeshPrimitive::Points, 6}; + Trade::MeshData c{MeshPrimitive::Points, 2}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 0); + CORRADE_COMPARE(dst.vertexCount(), 11); + CORRADE_VERIFY(!dst.vertexData()); + CORRADE_VERIFY(!dst.isIndexed()); +} + +/* MSVC 2015 doesn't like unnamed bitfields in local structs, so thhis has to + be outside */ +struct VertexDataNonInterleaved { + Vector2 texcoords1[2]; + Vector2 texcoords2[2]; + Int:32; + Int:32; + Vector3 position[2]; +}; + +void ConcatenateTest::concatenateOne() { + const VertexDataNonInterleaved vertexData[]{{ + {{0.1f, 0.2f}, + {0.3f, 0.4f}}, + {{0.5f, 0.6f}, + {0.7f, 0.8f}}, + {{1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}} + }}; + const UnsignedByte indices[]{1, 0, 1}; + Trade::MeshData a{MeshPrimitive::Points, + {}, indices, Trade::MeshIndexData{indices}, {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::arrayView(vertexData[0].texcoords1)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::arrayView(vertexData[0].texcoords2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(vertexData[0].position)}, + }}; + + Trade::MeshData dst = MeshTools::concatenate(a); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 3); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.1f, 0.2f}, + {0.3f, 0.4f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates, 1), + Containers::arrayView({ + {0.5f, 0.6f}, + {0.7f, 0.8f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 1, 0, 1 + }), TestSuite::Compare::Container); + + /* The mesh should get interleaved (w/o gaps) and owned */ + CORRADE_VERIFY(isInterleaved(dst)); + CORRADE_COMPARE(dst.attributeStride(0), 2*sizeof(Vector2) + sizeof(Vector3)); + CORRADE_COMPARE(dst.indexDataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(dst.vertexDataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); +} + +void ConcatenateTest::concatenateOneRvalue() { + Containers::Array vertexData{sizeof(Vector2)*4}; + auto positions = Containers::arrayCast(vertexData); + Containers::Array indexData{sizeof(UnsignedInt)*6}; + auto indices = Containers::arrayCast(indexData); + Trade::MeshAttributeData attributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions} + }; + + /* The result should be just a pass-through, as both index and vertex data + are already owned */ + Trade::MeshData dst = MeshTools::concatenate(Trade::MeshData{ + MeshPrimitive::Triangles, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), Trade::meshAttributeDataNonOwningArray(attributeData)}, + /* Explicitly pass an empty init list to ensure this overload is + covered as well */ + std::initializer_list>{}); + CORRADE_COMPARE(dst.indexData().data(), static_cast(indices.data())); + CORRADE_COMPARE(dst.vertexData().data(), static_cast(positions.data())); +} + +void ConcatenateTest::concatenateInto() { + Containers::Array attributeData{2}; + Containers::Array vertexData; + Containers::Array indexData; + arrayResize(vertexData, Containers::DirectInit, (sizeof(Vector2) + sizeof(Vector3))*7, '\xff'); + arrayResize(vertexData, 0); + arrayResize(indexData, Containers::DirectInit, sizeof(UnsignedInt)*9, '\xff'); + arrayResize(indexData, 0); + const void* attributeDataPointer = attributeData; + const void* vertexDataPointer = vertexData; + const void* indexDataPointer = indexData; + + attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}; + attributeData[1] = Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, nullptr}; + Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; + Trade::MeshData dst{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; + + const Vector2 positionsA[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + {-1.0f, 1.0f}, + { 1.0f, 1.0f} + }; + const UnsignedShort indicesA[]{ + 0, 1, 2, 2, 1, 3 + }; + Trade::MeshData a{MeshPrimitive::Triangles, + {}, indicesA, Trade::MeshIndexData{indicesA}, + {}, positionsA, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionsA)} + }}; + + const Vector2 positionsB[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }; + Trade::MeshData b{MeshPrimitive::Triangles, + {}, positionsB, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionsB)} + }}; + + MeshTools::concatenateInto(dst, {a, b}); + CORRADE_COMPARE(dst.attributeCount(), 2); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + {-1.0f, 1.0f}, + { 1.0f, 1.0f}, + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }), TestSuite::Compare::Container); + /* The normal isn't present in any attribute and thus should be zeroed out + (*not* the whatever garbage present there from before) */ + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({ + {}, {}, {}, {}, {}, {}, {} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 0, 1, 2, 2, 1, 3, + 4, 5, 6 + }), TestSuite::Compare::Container); + + /* Verify that no reallocation happened */ + CORRADE_COMPARE(dst.attributeData().size(), 2); + CORRADE_COMPARE(dst.attributeData().data(), attributeDataPointer); + CORRADE_COMPARE(dst.vertexData().size(), 7*(sizeof(Vector2) + sizeof(Vector3))); + CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); + CORRADE_COMPARE(dst.indexData().size(), 9*sizeof(UnsignedInt)); + CORRADE_COMPARE(dst.indexData().data(), indexDataPointer); +} + +void ConcatenateTest::concatenateIntoNoIndexArray() { + Containers::Array attributeData{1}; + Containers::Array vertexData; + Containers::Array indexData; + arrayReserve(vertexData, sizeof(Vector2)*3); + arrayReserve(indexData, sizeof(UnsignedInt)); + const void* attributeDataPointer = attributeData; + const void* vertexDataPointer = vertexData; + + attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}; + Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; + Trade::MeshData dst{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; + CORRADE_VERIFY(dst.isIndexed()); + + const Vector2 positions[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }; + Trade::MeshData a{MeshPrimitive::Triangles, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positions)} + }}; + + MeshTools::concatenateInto(dst, {a}); + CORRADE_COMPARE(dst.attributeCount(), 1); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }), TestSuite::Compare::Container); + + /* The index array gets removed, but no reallocation happens for the other + two */ + CORRADE_VERIFY(!dst.isIndexed()); + CORRADE_COMPARE(dst.attributeData().size(), 1); + CORRADE_COMPARE(dst.attributeData().data(), attributeDataPointer); + CORRADE_COMPARE(dst.vertexData().size(), 3*sizeof(Vector2)); + CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); +} + +void ConcatenateTest::concatenateIntoNonOwnedAttributeArray() { + Containers::Array vertexData; + arrayReserve(vertexData, sizeof(Vector2)*3); + const void* vertexDataPointer = vertexData; + + const Trade::MeshAttributeData attributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr} + }; + Trade::MeshData dst{MeshPrimitive::Triangles, + std::move(vertexData), Trade::meshAttributeDataNonOwningArray(attributeData)}; + + const Vector2 positions[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }; + Trade::MeshData a{MeshPrimitive::Triangles, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positions)} + }}; + + MeshTools::concatenateInto(dst, {a}); + CORRADE_COMPARE(dst.attributeCount(), 1); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }), TestSuite::Compare::Container); + + /* Reallocation happens only for the attribute data as it's not owned */ + CORRADE_VERIFY(!dst.isIndexed()); + CORRADE_COMPARE(dst.attributeData().size(), 1); + CORRADE_VERIFY(dst.attributeData().data() != attributeData); + CORRADE_COMPARE(dst.vertexData().size(), 3*sizeof(Vector2)); + CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); +} + +void ConcatenateTest::concatenateUnsupportedPrimitive() { + Trade::MeshData a{MeshPrimitive::TriangleStrip, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenate(a); + MeshTools::concatenateInto(a, {a}); + CORRADE_COMPARE(out.str(), + "MeshTools::concatenate(): MeshPrimitive::TriangleStrip is not supported, turn it into a plain indexed mesh first\n" + "MeshTools::concatenateInto(): MeshPrimitive::TriangleStrip is not supported, turn it into a plain indexed mesh first\n"); +} + +void ConcatenateTest::concatenateInconsistentPrimitive() { + /* Things are a bit duplicated to test correct numbering */ + Trade::MeshData a{MeshPrimitive::Triangles, 0}; + Trade::MeshData b{MeshPrimitive::Lines, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenate(a, {a, b}); + MeshTools::concatenateInto(a, {a, b}); + CORRADE_COMPARE(out.str(), + "MeshTools::concatenate(): expected MeshPrimitive::Triangles but got MeshPrimitive::Lines in mesh 1\n" + "MeshTools::concatenateInto(): expected MeshPrimitive::Triangles but got MeshPrimitive::Lines in mesh 1\n"); +} + +void ConcatenateTest::concatenateInconsistentAttributeType() { + /* Things are a bit duplicated to test correct numbering */ + Trade::MeshData a{MeshPrimitive::Lines, nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3ubNormalized, nullptr} + }}; + Trade::MeshData b{MeshPrimitive::Lines, nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3usNormalized, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenate(a, {a, a, a, b}); + MeshTools::concatenateInto(a, {a, a, a, b}); + CORRADE_COMPARE(out.str(), + "MeshTools::concatenate(): expected VertexFormat::Vector3ubNormalized for attribute 2 (Trade::MeshAttribute::Color) but got VertexFormat::Vector3usNormalized in mesh 3 attribute 1\n" + "MeshTools::concatenateInto(): expected VertexFormat::Vector3ubNormalized for attribute 2 (Trade::MeshAttribute::Color) but got VertexFormat::Vector3usNormalized in mesh 3 attribute 1\n"); +} + +void ConcatenateTest::concatenateIntoNoMeshes() { + Trade::MeshData destination{MeshPrimitive::Triangles, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenateInto(destination, {}); + CORRADE_COMPARE(out.str(), "MeshTools::concatenateInto(): no meshes passed\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MeshTools::Test::ConcatenateTest) diff --git a/src/Magnum/MeshTools/Test/DuplicateTest.cpp b/src/Magnum/MeshTools/Test/DuplicateTest.cpp index a4ccce2955..8f638a9421 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -27,9 +27,13 @@ #include #include #include +#include #include "Magnum/Magnum.h" +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -38,19 +42,61 @@ struct DuplicateTest: TestSuite::Tester { void duplicate(); void duplicateOutOfBounds(); + #ifdef MAGNUM_BUILD_DEPRECATED void duplicateStl(); + #endif void duplicateInto(); void duplicateIntoWrongSize(); + + template void duplicateIntoErased(); + void duplicateIntoErasedWrongTypeSize(); + void duplicateIntoErasedNonContiguous(); + + template void duplicateErasedIndicesIntoErased(); + void duplicateErasedIndicesIntoErasedNonContiguous(); + void duplicateErasedIndicesIntoErasedWrongTypeSize(); + + template void duplicateMeshData(); + void duplicateMeshDataNotIndexed(); + void duplicateMeshDataExtra(); + void duplicateMeshDataExtraEmpty(); + void duplicateMeshDataExtraWrongCount(); + void duplicateMeshDataExtraOffsetOnly(); + void duplicateMeshDataNoAttributes(); }; DuplicateTest::DuplicateTest() { addTests({&DuplicateTest::duplicate, &DuplicateTest::duplicateOutOfBounds, + #ifdef MAGNUM_BUILD_DEPRECATED &DuplicateTest::duplicateStl, + #endif &DuplicateTest::duplicateInto, - &DuplicateTest::duplicateIntoWrongSize}); + &DuplicateTest::duplicateIntoWrongSize, + + &DuplicateTest::duplicateIntoErased, + &DuplicateTest::duplicateIntoErased, + &DuplicateTest::duplicateIntoErased, + &DuplicateTest::duplicateIntoErasedWrongTypeSize, + &DuplicateTest::duplicateIntoErasedNonContiguous, + + &DuplicateTest::duplicateErasedIndicesIntoErased, + &DuplicateTest::duplicateErasedIndicesIntoErased, + &DuplicateTest::duplicateErasedIndicesIntoErased, + &DuplicateTest::duplicateErasedIndicesIntoErasedNonContiguous, + &DuplicateTest::duplicateErasedIndicesIntoErasedWrongTypeSize, + + &DuplicateTest::duplicateMeshData, + &DuplicateTest::duplicateMeshData, + &DuplicateTest::duplicateMeshData, + &DuplicateTest::duplicateMeshDataNotIndexed, + &DuplicateTest::duplicateMeshDataExtra, + &DuplicateTest::duplicateMeshDataExtraEmpty, + &DuplicateTest::duplicateMeshDataExtraWrongCount, + &DuplicateTest::duplicateMeshDataExtraOffsetOnly, + &DuplicateTest::duplicateMeshDataNoAttributes}); } void DuplicateTest::duplicate() { @@ -58,9 +104,8 @@ void DuplicateTest::duplicate() { constexpr Int data[]{-7, 35, 12, -18}; CORRADE_COMPARE_AS((MeshTools::duplicate(indices, data)), - (Containers::Array{Containers::InPlaceInit, { - 35, 35, -7, -18, 12, 12 - }}), TestSuite::Compare::Container); + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); } void DuplicateTest::duplicateOutOfBounds() { @@ -75,10 +120,14 @@ void DuplicateTest::duplicateOutOfBounds() { "MeshTools::duplicateInto(): index 4 out of bounds for 4 elements\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED void DuplicateTest::duplicateStl() { + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(MeshTools::duplicate({1, 1, 0, 3, 2, 2}, std::vector{-7, 35, 12, -18}), (std::vector{35, 35, -7, -18, 12, 12})); + CORRADE_IGNORE_DEPRECATED_POP } +#endif void DuplicateTest::duplicateInto() { constexpr UnsignedByte indices[]{1, 1, 0, 3, 2, 2}; @@ -87,9 +136,8 @@ void DuplicateTest::duplicateInto() { MeshTools::duplicateInto(indices, data, output); CORRADE_COMPARE_AS(Containers::arrayView(output), - (Containers::Array{Containers::InPlaceInit, { - 35, 35, -7, -18, 12, 12 - }}), TestSuite::Compare::Container); + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); } void DuplicateTest::duplicateIntoWrongSize() { @@ -102,7 +150,250 @@ void DuplicateTest::duplicateIntoWrongSize() { MeshTools::duplicateInto(indices, data, output); CORRADE_COMPARE(out.str(), - "MeshTools::duplicateInto(): bad output size, expected 6 but got 5\n"); + "MeshTools::duplicateInto(): index array and output size don't match, expected 6 but got 5\n"); +} + +template void DuplicateTest::duplicateIntoErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + constexpr T indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Int output[6]; + + MeshTools::duplicateInto( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE_AS(Containers::arrayView(output), + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateIntoErasedWrongTypeSize() { + constexpr UnsignedByte indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): input and output type size doesn't match, expected 4 but got 2\n"); +} + +void DuplicateTest::duplicateIntoErasedNonContiguous() { + constexpr UnsignedByte indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)).every({1, 2}), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): second view dimension is not contiguous\n"); +} + +template void DuplicateTest::duplicateErasedIndicesIntoErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + constexpr T indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Int output[6]; + + MeshTools::duplicateInto( + Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE_AS(Containers::arrayView(output), + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateErasedIndicesIntoErasedWrongTypeSize() { + constexpr char indices[6*3]{}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::StridedArrayView2D{indices, {6, 3}}.every(2), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): expected index type size 1, 2 or 4 but got 3\n"); +} + +void DuplicateTest::duplicateErasedIndicesIntoErasedNonContiguous() { + constexpr char indices[3*6]{}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::StridedArrayView2D{indices, {3, 3}, {6, 2}}, + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)).every({1, 2}), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): second index view dimension is not contiguous\n"); +} + +template void DuplicateTest::duplicateMeshData() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 2, 1, 0}; + struct { + Vector2 positions[3]; + Vector3 normals[3]; + } vertexData{ + {{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}, + {Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()} + }; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, + {}, Containers::arrayView(&vertexData, 1), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData.positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData.normals)} + }}; + + Trade::MeshData duplicated = MeshTools::duplicate(data); + CORRADE_VERIFY(MeshTools::isInterleaved(duplicated)); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 2); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}, + {1.0f, -0.5f}, {0.87f, 1.1f}, {1.3f, 0.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({ + Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis(), + Vector3::zAxis(), Vector3::yAxis(), Vector3::xAxis() + }), TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateMeshDataNotIndexed() { + std::ostringstream out; + Error redirectError{&out}; + MeshTools::duplicate(Trade::MeshData{MeshPrimitive::Points, 0}); + CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): mesh data not indexed\n"); +} + +void DuplicateTest::duplicateMeshDataExtra() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + Trade::MeshData duplicated = MeshTools::duplicate(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(duplicated)); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 2); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}, + {1.0f, -0.5f}, {0.87f, 1.1f}, {1.3f, 0.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({ + Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis(), + Vector3::zAxis(), Vector3::yAxis(), Vector3::xAxis() + }), TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateMeshDataExtraEmpty() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Trade::MeshData duplicated = MeshTools::duplicate(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, nullptr} + }); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 2); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}, + {1.0f, -0.5f}, {0.87f, 1.1f}, {1.3f, 0.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE(duplicated.attributeStride(Trade::MeshAttribute::Normal), 24); + CORRADE_COMPARE(duplicated.attributeOffset(Trade::MeshAttribute::Normal), 12); +} + +void DuplicateTest::duplicateMeshDataExtraWrongCount() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::duplicate(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)} + }); + CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): extra attribute 1 expected to have 3 items but got 2\n"); +} + +void DuplicateTest::duplicateMeshDataExtraOffsetOnly() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, 3}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::duplicate(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, 3, 5, 14} + }); + CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): extra attribute 1 is offset-only, which is not supported\n"); +} + +void DuplicateTest::duplicateMeshDataNoAttributes() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 3}; + + Trade::MeshData duplicated = MeshTools::duplicate(data, {}); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 0); + CORRADE_VERIFY(!duplicated.vertexData()); } }}}} diff --git a/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp b/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp index cade8733e8..626aa6040b 100644 --- a/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp @@ -24,7 +24,9 @@ */ #include +#include #include +#include #include #include "Magnum/Math/Vector3.h" @@ -36,44 +38,107 @@ struct FlipNormalsTest: TestSuite::Tester { explicit FlipNormalsTest(); void wrongIndexCount(); - void flipFaceWinding(); + template void flipFaceWinding(); + template void flipFaceWindingErased(); void flipNormals(); + + template void flipNormalsFaceWinding(); + template void flipNormalsFaceWindingErased(); }; FlipNormalsTest::FlipNormalsTest() { addTests({&FlipNormalsTest::wrongIndexCount, - &FlipNormalsTest::flipFaceWinding, - &FlipNormalsTest::flipNormals}); + &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipFaceWindingErased, + &FlipNormalsTest::flipFaceWindingErased, + &FlipNormalsTest::flipFaceWindingErased, + &FlipNormalsTest::flipNormals, + + &FlipNormalsTest::flipNormalsFaceWinding, + &FlipNormalsTest::flipNormalsFaceWinding, + &FlipNormalsTest::flipNormalsFaceWinding, + &FlipNormalsTest::flipNormalsFaceWindingErased, + &FlipNormalsTest::flipNormalsFaceWindingErased, + &FlipNormalsTest::flipNormalsFaceWindingErased, + }); } void FlipNormalsTest::wrongIndexCount() { std::stringstream ss; Error redirectError{&ss}; - std::vector indices{0, 1}; - MeshTools::flipFaceWinding(indices); + UnsignedByte indices[2]; + MeshTools::flipFaceWindingInPlace(Containers::stridedArrayView(indices)); CORRADE_COMPARE(ss.str(), "MeshTools::flipNormals(): index count is not divisible by 3!\n"); } -void FlipNormalsTest::flipFaceWinding() { - std::vector indices{0, 1, 2, - 3, 4, 5}; - MeshTools::flipFaceWinding(indices); +template void FlipNormalsTest::flipFaceWinding() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + MeshTools::flipFaceWindingInPlace(indices); - CORRADE_COMPARE(indices, (std::vector{0, 2, 1, - 3, 5, 4})); + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); +} + +template void FlipNormalsTest::flipFaceWindingErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + MeshTools::flipFaceWindingInPlace(indices); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); } void FlipNormalsTest::flipNormals() { - std::vector normals{Vector3::xAxis(), - Vector3::yAxis(), - Vector3::zAxis()}; - MeshTools::flipNormals(normals); - - CORRADE_COMPARE(normals, (std::vector{-Vector3::xAxis(), - -Vector3::yAxis(), - -Vector3::zAxis()})); + Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + MeshTools::flipNormalsInPlace(normals); + + CORRADE_COMPARE_AS(Containers::arrayView(normals), + Containers::arrayView({ + -Vector3::xAxis(), -Vector3::yAxis(), -Vector3::zAxis() + }), TestSuite::Compare::Container); +} + +template void FlipNormalsTest::flipNormalsFaceWinding() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + MeshTools::flipNormalsInPlace(indices, normals); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(normals), + Containers::arrayView({ + -Vector3::xAxis(), -Vector3::yAxis(), -Vector3::zAxis() + }), TestSuite::Compare::Container); +} + +template void FlipNormalsTest::flipNormalsFaceWindingErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + MeshTools::flipNormalsInPlace( + Containers::arrayCast<2, char>(Containers::stridedArrayView(indices)), + normals); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(normals), + Containers::arrayView({ + -Vector3::xAxis(), -Vector3::yAxis(), -Vector3::zAxis() + }), TestSuite::Compare::Container); } }}}} diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index 872e532e1c..38bbd3892e 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -37,7 +37,7 @@ #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/Primitives/Cylinder.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -51,15 +51,20 @@ struct GenerateNormalsTest: TestSuite::Tester { void flatWrongCount(); void flatIntoWrongSize(); - void smoothTwoTriangles(); + template void smoothTwoTriangles(); void smoothCube(); void smoothBeveledCube(); void smoothCylinder(); void smoothZeroAreaTriangle(); void smoothNanPosition(); void smoothWrongCount(); + void smoothOutOfBounds(); void smoothIntoWrongSize(); + template void smoothErased(); + void smoothErasedNonContiguous(); + void smoothErasedWrongIndexSize(); + void benchmarkFlat(); void benchmarkSmooth(); }; @@ -72,14 +77,23 @@ GenerateNormalsTest::GenerateNormalsTest() { &GenerateNormalsTest::flatWrongCount, &GenerateNormalsTest::flatIntoWrongSize, - &GenerateNormalsTest::smoothTwoTriangles, + &GenerateNormalsTest::smoothTwoTriangles, + &GenerateNormalsTest::smoothTwoTriangles, + &GenerateNormalsTest::smoothTwoTriangles, &GenerateNormalsTest::smoothCube, &GenerateNormalsTest::smoothBeveledCube, &GenerateNormalsTest::smoothCylinder, &GenerateNormalsTest::smoothZeroAreaTriangle, &GenerateNormalsTest::smoothNanPosition, &GenerateNormalsTest::smoothWrongCount, - &GenerateNormalsTest::smoothIntoWrongSize}); + &GenerateNormalsTest::smoothOutOfBounds, + &GenerateNormalsTest::smoothIntoWrongSize, + + &GenerateNormalsTest::smoothErased, + &GenerateNormalsTest::smoothErased, + &GenerateNormalsTest::smoothErased, + &GenerateNormalsTest::smoothErasedNonContiguous, + &GenerateNormalsTest::smoothErasedWrongIndexSize}); addBenchmarks({&GenerateNormalsTest::benchmarkFlat, &GenerateNormalsTest::benchmarkSmooth}, 150); @@ -98,14 +112,14 @@ constexpr Vector3 TwoTriangles[]{ void GenerateNormalsTest::flat() { CORRADE_COMPARE_AS(generateFlatNormals(TwoTriangles), - (Containers::Array{Containers::InPlaceInit, { + Containers::arrayView({ Vector3::zAxis(), Vector3::zAxis(), Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis() - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -155,20 +169,21 @@ void GenerateNormalsTest::flatIntoWrongSize() { CORRADE_COMPARE(out.str(), "MeshTools::generateFlatNormalsInto(): bad output size, expected 6 but got 7\n"); } -void GenerateNormalsTest::smoothTwoTriangles() { - const UnsignedInt indices[]{0, 1, 2, 3, 4, 5}; +template void GenerateNormalsTest::smoothTwoTriangles() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{0, 1, 2, 3, 4, 5}; /* Should generate the same output as flat normals */ - CORRADE_COMPARE_AS( - generateSmoothNormals(Containers::stridedArrayView(indices), TwoTriangles), - (Containers::Array{Containers::InPlaceInit, { + CORRADE_COMPARE_AS(generateSmoothNormals(indices, TwoTriangles), + Containers::arrayView({ Vector3::zAxis(), Vector3::zAxis(), Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis() - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } void GenerateNormalsTest::smoothCube() { @@ -193,9 +208,8 @@ void GenerateNormalsTest::smoothCube() { }; /* Normals should be the same as positions, only normalized */ - CORRADE_COMPARE_AS( - generateSmoothNormals(Containers::stridedArrayView(indices), positions), - (Containers::Array{Containers::InPlaceInit, { + CORRADE_COMPARE_AS(generateSmoothNormals(indices, positions), + Containers::arrayView({ positions[0]/Constants::sqrt3(), positions[1]/Constants::sqrt3(), positions[2]/Constants::sqrt3(), @@ -204,10 +218,9 @@ void GenerateNormalsTest::smoothCube() { positions[5]/Constants::sqrt3(), positions[6]/Constants::sqrt3(), positions[7]/Constants::sqrt3() - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } - constexpr Vector3 BeveledCubePositions[] { {-1.0f, -0.6f, 1.1f}, { 1.0f, -0.6f, 1.1f}, @@ -283,9 +296,8 @@ void GenerateNormalsTest::smoothBeveledCube() { Vector3 z{0.0462723f, 0.0754969f, 0.996072f}; Vector3 x{0.996072f, 0.0754969f, 0.0462723f}; Vector3 y{0.0467958f, 0.997808f, 0.0467958f}; - CORRADE_COMPARE_AS(generateSmoothNormals( - Containers::stridedArrayView(BeveledCubeIndices), BeveledCubePositions), - (Containers::Array{Containers::InPlaceInit, { + CORRADE_COMPARE_AS(generateSmoothNormals(BeveledCubeIndices, BeveledCubePositions), + Containers::arrayView({ z*Math::sign(BeveledCubePositions[ 0]), z*Math::sign(BeveledCubePositions[ 1]), z*Math::sign(BeveledCubePositions[ 2]), /* +Z */ @@ -315,17 +327,18 @@ void GenerateNormalsTest::smoothBeveledCube() { x*Math::sign(BeveledCubePositions[21]), x*Math::sign(BeveledCubePositions[22]), /* -X */ x*Math::sign(BeveledCubePositions[23]) - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } void GenerateNormalsTest::smoothCylinder() { - const Trade::MeshData3D data = Primitives::cylinderSolid(1, 5, 1.0f); + const Trade::MeshData data = Primitives::cylinderSolid(1, 5, 1.0f); /* Output should be exactly the same as the cylinder normals */ CORRADE_COMPARE_AS(Containers::arrayView(generateSmoothNormals( - Containers::stridedArrayView(data.indices()), - Containers::stridedArrayView(data.positions(0)))), - Containers::arrayView(data.normals(0)), TestSuite::Compare::Container); + data.indices(), + data.attribute(Trade::MeshAttribute::Position))), + data.attribute(Trade::MeshAttribute::Normal), + TestSuite::Compare::Container); } void GenerateNormalsTest::smoothZeroAreaTriangle() { @@ -341,8 +354,7 @@ void GenerateNormalsTest::smoothZeroAreaTriangle() { 0, 1, 2, 1, 2, 1 }; - CORRADE_COMPARE_AS(generateSmoothNormals( - Containers::stridedArrayView(indices), positions), + CORRADE_COMPARE_AS(generateSmoothNormals(indices, positions), (Containers::Array{Containers::InPlaceInit, { Vector3::zAxis(), Vector3::zAxis(), @@ -358,21 +370,17 @@ void GenerateNormalsTest::smoothNanPosition() { { 0.0f, Constants::nan(), 0.0f}, }; - /* Second triangle is just an edge, so it shouldn't contribute to the first - triangle normal */ + /* Second triangle will poison a part of the first with NaNs, but it won't + crash */ constexpr UnsignedInt indices[] { - 0, 1, 2, 1, 2, 1 + 0, 1, 2, 1, 2, 3 }; - Containers::Array generated = generateSmoothNormals( - Containers::stridedArrayView(indices), positions); - CORRADE_COMPARE_AS(generated.prefix(3), - (Containers::Array{Containers::InPlaceInit, { - Vector3::zAxis(), - Vector3::zAxis(), - Vector3::zAxis() - }}), TestSuite::Compare::Container>); - CORRADE_COMPARE(Math::isNan(generated[3]), BoolVector3{0x7}); + Containers::Array generated = generateSmoothNormals(indices, positions); + CORRADE_COMPARE(generated[0], Vector3::zAxis()); + CORRADE_VERIFY(Math::isNan(generated[1]).all()); + CORRADE_VERIFY(Math::isNan(generated[2]).all()); + CORRADE_VERIFY(Math::isNan(generated[3]).all()); } void GenerateNormalsTest::smoothWrongCount() { @@ -381,10 +389,20 @@ void GenerateNormalsTest::smoothWrongCount() { const UnsignedByte indices[7]{}; const Vector3 positions[1]; - generateSmoothNormals(Containers::stridedArrayView(indices), positions); + generateSmoothNormals(indices, positions); CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): index count not divisible by 3\n"); } +void GenerateNormalsTest::smoothOutOfBounds() { + std::stringstream out; + Error redirectError{&out}; + + const Vector3 positions[2]; + const UnsignedInt indices[] { 0, 1, 2 }; + generateSmoothNormals(indices, positions); + CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): index 2 out of bounds for 2 elements\n"); +} + void GenerateNormalsTest::smoothIntoWrongSize() { std::stringstream out; Error redirectError{&out}; @@ -392,7 +410,7 @@ void GenerateNormalsTest::smoothIntoWrongSize() { const UnsignedByte indices[6]{}; const Vector3 positions[3]; Vector3 normals[4]; - generateSmoothNormalsInto(Containers::stridedArrayView(indices), positions, normals); + generateSmoothNormalsInto(indices, positions, normals); CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): bad output size, expected 3 but got 4\n"); } @@ -412,14 +430,51 @@ void GenerateNormalsTest::benchmarkFlat() { void GenerateNormalsTest::benchmarkSmooth() { Containers::Array normals{Containers::NoInit, Containers::arraySize(BeveledCubePositions)}; CORRADE_BENCHMARK(10) { - generateSmoothNormalsInto( - Containers::stridedArrayView(BeveledCubeIndices), - BeveledCubePositions, normals); + generateSmoothNormalsInto(BeveledCubeIndices, BeveledCubePositions, normals); } CORRADE_COMPARE(Math::min(normals), (Vector3{-0.996072f, -0.997808f, -0.996072f})); } +template void GenerateNormalsTest::smoothErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{0, 1, 2, 3, 4, 5}; + + /* Should generate the same output as flat normals */ + CORRADE_COMPARE_AS(generateSmoothNormals(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), TwoTriangles), + Containers::arrayView({ + Vector3::zAxis(), + Vector3::zAxis(), + Vector3::zAxis(), + -Vector3::zAxis(), + -Vector3::zAxis(), + -Vector3::zAxis() + }), TestSuite::Compare::Container); +} + +void GenerateNormalsTest::smoothErasedNonContiguous() { + const char indices[6*4]{}; + const Vector3 positions[3]; + + std::stringstream out; + Error redirectError{&out}; + generateSmoothNormals(Containers::StridedArrayView2D{indices, {6, 2}, {4, 2}}, positions); + CORRADE_COMPARE(out.str(), + "MeshTools::generateSmoothNormalsInto(): second index view dimension is not contiguous\n"); +} + +void GenerateNormalsTest::smoothErasedWrongIndexSize() { + const char indices[6*3]{}; + const Vector3 positions[3]; + + std::stringstream out; + Error redirectError{&out}; + generateSmoothNormals(Containers::StridedArrayView2D{indices, {6, 3}}.every(2), positions); + CORRADE_COMPARE(out.str(), + "MeshTools::generateSmoothNormalsInto(): expected index type size 1, 2 or 4 but got 3\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::GenerateNormalsTest) diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index 89de1aee88..142e381f78 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -26,11 +26,14 @@ #include #include #include +#include #include #include #include +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -41,10 +44,46 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void attributeCountGaps(); void stride(); void strideGaps(); - void write(); - void writeGaps(); + void interleave(); + void interleaveGaps(); + void interleaveEmpty(); void interleaveInto(); + + void isInterleaved(); + void isInterleavedEmpty(); + void isInterleavedSingleAttribute(); + void isInterleavedGaps(); + void isInterleavedAliased(); + void isInterleavedUnordered(); + void isInterleavedAttributeAcrossStride(); + + void interleavedData(); + void interleavedDataNoAttributes(); + void interleavedDataNoVertices(); + void interleavedDataNotInterleaved(); + + void interleavedLayout(); + void interleavedLayoutExtra(); + void interleavedLayoutExtraAliased(); + void interleavedLayoutExtraTooNegativePadding(); + void interleavedLayoutExtraOnly(); + void interleavedLayoutAlreadyInterleaved(); + void interleavedLayoutAlreadyInterleavedAliased(); + void interleavedLayoutAlreadyInterleavedExtra(); + void interleavedLayoutNothing(); + void interleavedLayoutRvalue(); + + void interleaveMeshData(); + void interleaveMeshDataIndexed(); + void interleaveMeshDataExtra(); + void interleaveMeshDataExtraEmpty(); + void interleaveMeshDataExtraOriginalEmpty(); + void interleaveMeshDataExtraWrongCount(); + void interleaveMeshDataExtraOffsetOnly(); + void interleaveMeshDataAlreadyInterleavedMove(); + void interleaveMeshDataAlreadyInterleavedMoveNonOwned(); + void interleaveMeshDataNothing(); }; InterleaveTest::InterleaveTest() { @@ -52,10 +91,46 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::attributeCountGaps, &InterleaveTest::stride, &InterleaveTest::strideGaps, - &InterleaveTest::write, - &InterleaveTest::writeGaps, + &InterleaveTest::interleave, + &InterleaveTest::interleaveGaps, + &InterleaveTest::interleaveEmpty, + + &InterleaveTest::interleaveInto, + + &InterleaveTest::isInterleaved, + &InterleaveTest::isInterleavedEmpty, + &InterleaveTest::isInterleavedSingleAttribute, + &InterleaveTest::isInterleavedGaps, + &InterleaveTest::isInterleavedAliased, + &InterleaveTest::isInterleavedUnordered, + &InterleaveTest::isInterleavedAttributeAcrossStride, + + &InterleaveTest::interleavedData, + &InterleaveTest::interleavedDataNoAttributes, + &InterleaveTest::interleavedDataNoVertices, + &InterleaveTest::interleavedDataNotInterleaved, + + &InterleaveTest::interleavedLayout, + &InterleaveTest::interleavedLayoutExtra, + &InterleaveTest::interleavedLayoutExtraAliased, + &InterleaveTest::interleavedLayoutExtraTooNegativePadding, + &InterleaveTest::interleavedLayoutExtraOnly, + &InterleaveTest::interleavedLayoutAlreadyInterleaved, + &InterleaveTest::interleavedLayoutAlreadyInterleavedAliased, + &InterleaveTest::interleavedLayoutAlreadyInterleavedExtra, + &InterleaveTest::interleavedLayoutNothing, + &InterleaveTest::interleavedLayoutRvalue, - &InterleaveTest::interleaveInto}); + &InterleaveTest::interleaveMeshData, + &InterleaveTest::interleaveMeshDataIndexed, + &InterleaveTest::interleaveMeshDataExtra, + &InterleaveTest::interleaveMeshDataExtraEmpty, + &InterleaveTest::interleaveMeshDataExtraOriginalEmpty, + &InterleaveTest::interleaveMeshDataExtraWrongCount, + &InterleaveTest::interleaveMeshDataExtraOffsetOnly, + &InterleaveTest::interleaveMeshDataAlreadyInterleavedMove, + &InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned, + &InterleaveTest::interleaveMeshDataNothing}); } void InterleaveTest::attributeCount() { @@ -87,7 +162,7 @@ void InterleaveTest::strideGaps() { CORRADE_COMPARE((Implementation::Stride{}(2, std::vector(), 1, std::vector(), 12)), std::size_t(20)); } -void InterleaveTest::write() { +void InterleaveTest::interleave() { const Containers::Array data = MeshTools::interleave( std::vector{0, 1, 2}, std::vector{3, 4, 5}, @@ -108,7 +183,7 @@ void InterleaveTest::write() { } } -void InterleaveTest::writeGaps() { +void InterleaveTest::interleaveGaps() { const Containers::Array data = MeshTools::interleave( std::vector{0, 1, 2}, 3, std::vector{3, 4, 5}, @@ -131,6 +206,11 @@ void InterleaveTest::writeGaps() { } } +void InterleaveTest::interleaveEmpty() { + const Containers::Array data = MeshTools::interleave(std::vector{}, 2); + CORRADE_COMPARE(data.size(), 0); +} + void InterleaveTest::interleaveInto() { Containers::Array data{Containers::InPlaceInit, { 0x11, 0x33, 0x55, 0x77, 0x11, 0x33, 0x55, 0x77, 0x11, 0x33, 0x55, 0x77, @@ -159,6 +239,720 @@ void InterleaveTest::interleaveInto() { } } +void InterleaveTest::isInterleaved() { + /* Interleaved; testing also initial offset */ + { + Containers::Array vertexData{100 + 3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 100), 3, 20}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 100 + 8), 3, 20}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + } + + /* One after another */ + { + Containers::Array vertexData{100 + 3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.suffix(100).prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(100).suffix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + } +} + +void InterleaveTest::isInterleavedEmpty() { + Trade::MeshData data{MeshPrimitive::Triangles, 5}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedSingleAttribute() { + Containers::Array vertexData{3*8}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedGaps() { + Containers::Array vertexData{3*40}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 5), 3, 40}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 24), 3, 40}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedAliased() { + /* Normals share first two components with positions */ + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedUnordered() { + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + + /* Normals specified first even though they're ordered after positions */ + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {normals, positions}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedAttributeAcrossStride() { + /* Data slightly larger */ + Containers::Array vertexData{5 + 3*30 + 3}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 5), 3, 30}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + /* 23 + 12 is 35, which still fits into the stride after + subtracting the initial offset; 24 not */ + reinterpret_cast(vertexData.data() + 23), 3, 30}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), + {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + vertexData = data.releaseVertexData(); + Trade::MeshAttributeData normals2{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 24), 3, 30}}; + Trade::MeshData data2{MeshPrimitive::Triangles, + std::move(vertexData), {positions, normals2}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data2)); +} + +void InterleaveTest::interleavedData() { + Containers::Array vertexData{100 + 3*40}; + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.data() + 100 + 24), 3, 40}; + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.data() + 100 + 5), 3, 40}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions} + }}; + + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + Containers::StridedArrayView2D interleaved = MeshTools::interleavedData(data); + CORRADE_COMPARE(interleaved.data(), positions.data()); + CORRADE_COMPARE(interleaved.size()[0], 3); + CORRADE_COMPARE(interleaved.size()[1], 31); + CORRADE_COMPARE(interleaved.stride()[0], 40); + CORRADE_COMPARE(interleaved.stride()[1], 1); +} + +void InterleaveTest::interleavedDataNoAttributes() { + char a[1]; + Trade::MeshData data{MeshPrimitive::Lines, {}, a, {}, 15}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + Containers::StridedArrayView2D interleaved = MeshTools::interleavedData(data); + CORRADE_COMPARE(interleaved.data(), static_cast(a)); + CORRADE_COMPARE(interleaved.size()[0], 15); + CORRADE_COMPARE(interleaved.size()[1], 0); + CORRADE_COMPARE(interleaved.stride()[0], 0); + CORRADE_COMPARE(interleaved.stride()[1], 1); +} + +void InterleaveTest::interleavedDataNoVertices() { + struct Vertex { + Vector3 normal; + Vector3 position; + }; + Vertex a[1]; + Trade::MeshData data{MeshPrimitive::Triangles, {}, a, { + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(a, &a[0].normal, 0, sizeof(Vertex))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(a, &a[0].position, 0, sizeof(Vertex))} + }}; + + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + Containers::StridedArrayView2D interleaved = MeshTools::interleavedData(data); + CORRADE_COMPARE(interleaved.data(), static_cast(a)); + CORRADE_COMPARE(interleaved.size()[0], 0); + CORRADE_COMPARE(interleaved.size()[1], sizeof(Vertex)); + CORRADE_COMPARE(interleaved.stride()[0], sizeof(Vertex)); + CORRADE_COMPARE(interleaved.stride()[1], 1); +} + +void InterleaveTest::interleavedDataNotInterleaved() { + Containers::Array vertexData{100 + 3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.suffix(100).prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(100).suffix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleavedData(data); + CORRADE_COMPARE(out.str(), "MeshTools::interleavedData(): the mesh is not interleaved\n"); +} + +void InterleaveTest::interleavedLayout() { + Containers::Array indexData{6}; + Containers::Array vertexData{3*20}; + + const Trade::MeshAttributeData attributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))} + }; + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + std::move(indexData), indices, std::move(vertexData), + /* Verify that interleavedLayout() won't attempt to modify the const + array (see interleavedLayoutRvalue()) */ + Trade::meshAttributeDataNonOwningArray(attributeData)}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 20); + CORRADE_COMPARE(layout.attributeStride(1), 20); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 8); + CORRADE_COMPARE(layout.vertexCount(), 10); + /* Needs to be like this so we can modify the data */ + CORRADE_COMPARE(layout.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_VERIFY(layout.vertexData()); + CORRADE_COMPARE(layout.vertexData().size(), 10*20); +} + +void InterleaveTest::interleavedLayoutExtra() { + Containers::Array vertexData{3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 7, { + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(15), + VertexFormat::UnsignedShort, nullptr}, + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{4} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 4); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeName(2), Trade::meshAttributeCustom(15)); + CORRADE_COMPARE(layout.attributeName(3), Trade::MeshAttribute::Color); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeFormat(2), VertexFormat::UnsignedShort); + CORRADE_COMPARE(layout.attributeFormat(3), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 40); + CORRADE_COMPARE(layout.attributeStride(1), 40); + CORRADE_COMPARE(layout.attributeStride(2), 40); + CORRADE_COMPARE(layout.attributeStride(3), 40); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 8); + CORRADE_COMPARE(layout.attributeOffset(2), 21); + CORRADE_COMPARE(layout.attributeOffset(3), 24); + CORRADE_COMPARE(layout.vertexCount(), 7); + CORRADE_COMPARE(layout.vertexData().size(), 7*40); +} + +void InterleaveTest::interleavedLayoutExtraAliased() { + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions}}; + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 100, { + /* Normals at the same place as positions */ + Trade::MeshAttributeData{-12}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, positions.data()} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 12); + CORRADE_COMPARE(layout.attributeStride(1), 12); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 0); /* aliases */ + CORRADE_COMPARE(layout.vertexCount(), 100); + CORRADE_COMPARE(layout.vertexData().size(), 100*12); +} + +void InterleaveTest::interleavedLayoutExtraTooNegativePadding() { + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleavedLayout(data, 100, { + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, positions.data()}, + Trade::MeshAttributeData{-25} + }); + CORRADE_COMPARE(out.str(), "MeshTools::interleavedLayout(): negative padding -25 in extra attribute 1 too large for stride 24\n"); +} + +void InterleaveTest::interleavedLayoutExtraOnly() { + Trade::MeshData data{MeshPrimitive::Triangles, 0}; + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, nullptr} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 24); + CORRADE_COMPARE(layout.attributeStride(1), 24); + CORRADE_COMPARE(layout.attributeOffset(0), 4); + CORRADE_COMPARE(layout.attributeOffset(1), 12); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*24); +} + +void InterleaveTest::interleavedLayoutAlreadyInterleaved() { + Containers::Array indexData{6}; + /* Test also removing the initial offset */ + Containers::Array vertexData{100 + 3*24}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100), 3, 24}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100 + 10), 3, 24}}; + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + /* Original stride should be preserved */ + CORRADE_COMPARE(layout.attributeStride(0), 24); + CORRADE_COMPARE(layout.attributeStride(1), 24); + /* Relative offsets should be preserved, but the initial one removed */ + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 10); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*24); +} + +void InterleaveTest::interleavedLayoutAlreadyInterleavedAliased() { + Containers::Array indexData{6}; + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 12); + CORRADE_COMPARE(layout.attributeStride(1), 12); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 0); /* aliases */ + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*12); +} + +void InterleaveTest::interleavedLayoutAlreadyInterleavedExtra() { + Containers::Array vertexData{100 + 3*24}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100), 3, 24}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100 + 10), 3, 24}}; + + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10, { + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(15), + VertexFormat::UnsignedShort, nullptr}, + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{4} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 4); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeName(2), Trade::meshAttributeCustom(15)); + CORRADE_COMPARE(layout.attributeName(3), Trade::MeshAttribute::Color); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeFormat(2), VertexFormat::UnsignedShort); + CORRADE_COMPARE(layout.attributeFormat(3), VertexFormat::Vector3); + /* Original stride should be preserved, with stride from extra attribs + added */ + CORRADE_COMPARE(layout.attributeStride(0), 24 + 20); + CORRADE_COMPARE(layout.attributeStride(1), 24 + 20); + CORRADE_COMPARE(layout.attributeStride(2), 24 + 20); + CORRADE_COMPARE(layout.attributeStride(3), 24 + 20); + /* Relative offsets should be preserved, but the initial one removed */ + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 10); + CORRADE_COMPARE(layout.attributeOffset(2), 25); + CORRADE_COMPARE(layout.attributeOffset(3), 28); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*44); +} + +void InterleaveTest::interleavedLayoutNothing() { + Trade::MeshData layout = MeshTools::interleavedLayout(Trade::MeshData{MeshPrimitive::Points, 25}, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 0); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_VERIFY(!layout.vertexData()); + CORRADE_COMPARE(layout.vertexData().size(), 0); +} + +void InterleaveTest::interleavedLayoutRvalue() { + Containers::Array indexData{6}; + Containers::Array vertexData{3*20}; + Containers::Array attributeData{2}; + attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + attributeData[1] = Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))}; + const void* originalAttributeData = attributeData.data(); + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + + /* Check that the attribute data array gets reused when moving a rvalue. + Explicitly passing an empty init list to verify the rvalue gets + propagated correctly through all functions. */ + Trade::MeshData layout = MeshTools::interleavedLayout(std::move(data), 10, + std::initializer_list{}); + CORRADE_VERIFY(layout.attributeData().data() == originalAttributeData); + + /* The rest is same as in interleavedLayout() */ + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 20); + CORRADE_COMPARE(layout.attributeStride(1), 20); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 8); + CORRADE_COMPARE(layout.vertexCount(), 10); + /* Needs to be like this so we can modify the data */ + CORRADE_COMPARE(layout.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_VERIFY(layout.vertexData()); + CORRADE_COMPARE(layout.vertexData().size(), 10*20); +} + +void InterleaveTest::interleaveMeshData() { + struct { + Vector2 positions[3]; + Vector3 normals[3]; + } vertexData{ + {{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}, + {Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()} + }; + Trade::MeshData data{MeshPrimitive::TriangleFan, {}, + Containers::arrayView(&vertexData, sizeof(vertexData)), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData.positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData.normals)} + }}; + + Trade::MeshData interleaved = MeshTools::interleave(data); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(vertexData.positions), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Normal), + Containers::stridedArrayView(vertexData.normals), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataIndexed() { + /* Testing also offset */ + UnsignedShort indexData[50 + 3]; + indexData[50] = 0; + indexData[51] = 2; + indexData[52] = 1; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(indexData), Trade::MeshIndexData{Containers::arrayView(indexData).suffix(50)}, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Trade::MeshData interleaved = MeshTools::interleave(data); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(interleaved.isIndexed()); + CORRADE_COMPARE(interleaved.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(interleaved.indexData().size(), 106); + CORRADE_COMPARE_AS(interleaved.indices(), + Containers::arrayView(indexData).suffix(50), + TestSuite::Compare::Container); + CORRADE_COMPARE(interleaved.attributeCount(), 1); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataExtra() { + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + Trade::MeshData interleaved = MeshTools::interleave(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Normal), + Containers::stridedArrayView(normals), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataExtraEmpty() { + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Trade::MeshData interleaved = MeshTools::interleave(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, nullptr} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); + CORRADE_COMPARE(interleaved.attributeStride(Trade::MeshAttribute::Normal), 24); + CORRADE_COMPARE(interleaved.attributeOffset(Trade::MeshAttribute::Normal), 12); +} + +void InterleaveTest::interleaveMeshDataExtraOriginalEmpty() { + Trade::MeshData data{MeshPrimitive::TriangleFan, 3}; + + /* Verify the original vertex count gets passed through */ + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData interleaved = MeshTools::interleave(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }); + + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 1); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataExtraWrongCount() { + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleave(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, Containers::arrayView(normals)} + }); + CORRADE_COMPARE(out.str(), "MeshTools::interleave(): extra attribute 1 expected to have 3 items but got 2\n"); +} + +void InterleaveTest::interleaveMeshDataExtraOffsetOnly() { + Trade::MeshData data{MeshPrimitive::TriangleFan, 5}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleave(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, 3, 5, 14} + }); + CORRADE_COMPARE(out.str(), "MeshTools::interleave(): extra attribute 1 is offset-only, which is not supported\n"); +} + +void InterleaveTest::interleaveMeshDataAlreadyInterleavedMove() { + Containers::Array indexData{4}; + auto indexView = Containers::arrayCast(indexData); + Containers::Array vertexData{3*24}; + Containers::StridedArrayView1D positionView{vertexData, + reinterpret_cast(vertexData.data()), 3, 24}; + Containers::StridedArrayView1D normalView{vertexData, + reinterpret_cast(vertexData.data() + 10), 3, 24}; + auto attributeData = Containers::array({ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView} + }); + const Trade::MeshAttributeData* attributePointer = attributeData; + + Trade::MeshData data{MeshPrimitive::TriangleFan, + std::move(indexData), Trade::MeshIndexData{indexView}, + std::move(vertexData), std::move(attributeData)}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + /* {} just to cover the initializer_list overload :P */ + Trade::MeshData interleaved = MeshTools::interleave(std::move(data), {}); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.indexCount(), 2); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE(interleaved.vertexCount(), 3); + /* Things got just moved without copying */ + CORRADE_VERIFY(interleaved.indexData().data() == static_cast(indexView.data())); + CORRADE_VERIFY(interleaved.attributeData().data() == attributePointer); + CORRADE_VERIFY(interleaved.vertexData().data() == positionView.data()); +} + +void InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned() { + Containers::Array indexData{4}; + auto indexView = Containers::arrayCast(indexData); + Containers::Array vertexData{3*24}; + Containers::StridedArrayView1D positionView{vertexData, + reinterpret_cast(vertexData.data()), 3, 24}; + Containers::StridedArrayView1D normalView{vertexData, + reinterpret_cast(vertexData.data() + 10), 3, 24}; + auto attributeData = Containers::array({ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView} + }); + const Trade::MeshAttributeData* attributePointer = attributeData; + + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indexData, Trade::MeshIndexData{indexView}, + {}, vertexData, std::move(attributeData)}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData interleaved = MeshTools::interleave(std::move(data)); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.indexCount(), 2); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE(interleaved.vertexCount(), 3); + /* The moved data array doesn't own these so things got copied */ + CORRADE_VERIFY(interleaved.indexData().data() != static_cast(indexView.data())); + CORRADE_VERIFY(interleaved.attributeData().data() != attributePointer); + CORRADE_VERIFY(interleaved.vertexData().data() != positionView.data()); +} + +void InterleaveTest::interleaveMeshDataNothing() { + Trade::MeshData interleaved = MeshTools::interleave(Trade::MeshData{MeshPrimitive::Points, 2}); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.attributeCount(), 0); + CORRADE_COMPARE(interleaved.vertexCount(), 2); + CORRADE_VERIFY(!interleaved.vertexData()); + CORRADE_COMPARE(interleaved.vertexData().size(), 0); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::InterleaveTest) diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index e10373f8eb..64a6cba7cc 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -23,7 +23,10 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include +#include #include "Magnum/Math/Vector2.h" #include "Magnum/MeshTools/RemoveDuplicates.h" @@ -34,16 +37,163 @@ struct RemoveDuplicatesTest: TestSuite::Tester { explicit RemoveDuplicatesTest(); void removeDuplicates(); + void removeDuplicatesNonContiguous(); + void removeDuplicatesIntoWrongOutputSize(); + template void removeDuplicatesIndexedInPlace(); + void removeDuplicatesIndexedInPlaceSmallType(); + void removeDuplicatesIndexedInPlaceEmptyIndices(); + void removeDuplicatesIndexedInPlaceEmptyIndicesVertices(); + + void removeDuplicatesFuzzyInPlace(); + #ifdef MAGNUM_BUILD_DEPRECATED + void removeDuplicatesFuzzyStl(); + #endif + template void removeDuplicatesFuzzyIndexedInPlace(); + void removeDuplicatesFuzzyIndexedInPlaceSmallType(); + void removeDuplicatesFuzzyIndexedInPlaceEmptyIndices(); + void removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices(); + + /* this is additionally regression-tested in PrimitivesIcosphereTest */ }; RemoveDuplicatesTest::RemoveDuplicatesTest() { - addTests({&RemoveDuplicatesTest::removeDuplicates}); + addTests({&RemoveDuplicatesTest::removeDuplicates, + &RemoveDuplicatesTest::removeDuplicatesNonContiguous, + &RemoveDuplicatesTest::removeDuplicatesIntoWrongOutputSize, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices, + + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlace, + #ifdef MAGNUM_BUILD_DEPRECATED + &RemoveDuplicatesTest::removeDuplicatesFuzzyStl, + #endif + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices}); } void RemoveDuplicatesTest::removeDuplicates() { + Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; + + std::pair, std::size_t> result = + MeshTools::removeDuplicates(Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(result.first), + Containers::arrayView({0, 1, 2, 0, 4, 5, 2, 1}), + TestSuite::Compare::Container); + + std::pair, std::size_t> resultInPlace = + MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(resultInPlace.first), + Containers::arrayView({0, 1, 2, 0, 3, 4, 2, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(resultInPlace.second), + Containers::arrayView({-15, 32, 24, 15, 7541}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesNonContiguous() { + Int data[8]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::removeDuplicates(Containers::arrayCast<2, const char>(Containers::arrayView(data)).every({1, 2})); + MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data)).every({1, 2})); + CORRADE_COMPARE(out.str(), + "MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous\n" + "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous\n"); +} + +void RemoveDuplicatesTest::removeDuplicatesIntoWrongOutputSize() { + Int data[8]{}; + UnsignedInt output[7]; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::removeDuplicatesInto( + Containers::arrayCast<2, const char>(Containers::arrayView(data)), + output); + MeshTools::removeDuplicatesInPlaceInto( + Containers::arrayCast<2, char>(Containers::arrayView(data)), + output); + CORRADE_COMPARE(out.str(), + "MeshTools::removeDuplicatesInto(): output index array has 7 elements but expected 8\n" + "MeshTools::removeDuplicatesInPlaceInto(): output index array has 7 elements but expected 8\n"); +} + +template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{3, 2, 0, 1, 7, 6, 4, 2, 5, 0}; + Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace(indices, + Containers::arrayCast<2, char>(Containers::arrayView(data))); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 0, 1, 1, 2, 3, 2, 4, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({-15, 32, 24, 15, 7541}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType() { + std::stringstream out; + Error redirectError{&out}; + + UnsignedByte indices[1]; + Vector2i data[256]{}; + MeshTools::removeDuplicatesIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesIndexedInPlace(): a 1-byte index type is too small for 256 vertices\n"); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices() { + Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; + + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace( + Containers::StridedArrayView1D{}, + Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({-15, 32, 24, 15, 7541}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices() { + CORRADE_COMPARE(MeshTools::removeDuplicatesIndexedInPlace( + Containers::StridedArrayView1D{}, {}), 0); +} + +void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlace() { /* Numbers with distance 1 should be merged, numbers with distance 2 should be kept. Testing both even-odd and odd-even sequence to verify that half-epsilon translations are applied properly. */ + Vector2i data[]{ + {1, 0}, + {2, 1}, + {0, 4}, + {1, 5} + }; + + std::pair, std::size_t> result = MeshTools::removeDuplicatesInPlace(Containers::stridedArrayView(data), 2); + CORRADE_COMPARE_AS(Containers::arrayView(result.first), + Containers::arrayView({0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second), + Containers::arrayView({{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void RemoveDuplicatesTest::removeDuplicatesFuzzyStl() { + /* Same but with implicit bloat. HEH HEH */ std::vector data{ {1, 0}, {2, 1}, @@ -51,12 +201,71 @@ void RemoveDuplicatesTest::removeDuplicates() { {1, 5} }; + CORRADE_IGNORE_DEPRECATED_PUSH const std::vector indices = MeshTools::removeDuplicates(data, 2); - CORRADE_COMPARE(indices, (std::vector{0, 0, 1, 1})); - CORRADE_COMPARE(data, (std::vector{ + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE_AS(indices, + (std::vector{0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data, + (std::vector{{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} +#endif + +template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + /* Same as above, but with an explicit index buffer */ + T indices[]{3, 2, 0, 1, 2, 3}; + Vector2i data[]{ {1, 0}, - {0, 4} - })); + {2, 1}, + {0, 4}, + {1, 5} + }; + + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::stridedArrayView(data), 2); + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({1, 1, 0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType() { + std::stringstream out; + Error redirectError{&out}; + + UnsignedByte indices[1]; + Vector2i data[256]{}; + MeshTools::removeDuplicatesIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::stridedArrayView(data), 2); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesIndexedInPlace(): a 1-byte index type is too small for 256 vertices\n"); +} + +void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices() { + Vector2i data[]{ + {1, 0}, + {2, 1}, + {0, 4}, + {1, 5} + }; + + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace( + Containers::StridedArrayView1D{}, + Containers::stridedArrayView(data), 2); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices() { + CORRADE_COMPARE((MeshTools::removeDuplicatesIndexedInPlace({}, {}, 2)), 0); } }}}} diff --git a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp index f0452e4785..4d6f5ab508 100644 --- a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp @@ -23,14 +23,16 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Math/Vector4.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Subdivide.h" #include "Magnum/Primitives/Icosphere.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -39,12 +41,14 @@ struct SubdivideRemoveDuplicatesBenchmark: TestSuite::Tester { void subdivide(); void subdivideAndRemoveDuplicatesAfter(); + void subdivideAndRemoveDuplicatesAfterInPlace(); void subdivideAndRemoveDuplicatesInBetween(); }; SubdivideRemoveDuplicatesBenchmark::SubdivideRemoveDuplicatesBenchmark() { addBenchmarks({&SubdivideRemoveDuplicatesBenchmark::subdivide, &SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfter, + &SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfterInPlace, &SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesInBetween}, 4); } @@ -55,36 +59,69 @@ namespace { } void SubdivideRemoveDuplicatesBenchmark::subdivide() { + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + CORRADE_BENCHMARK(3) { - Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); + Containers::Array indices; + arrayResize(indices, Containers::NoInit, icosphere.indexCount()); + Utility::copy(icosphere.indices(), indices); + + Containers::Array positions; + arrayResize(positions, Containers::NoInit, icosphere.vertexCount()); + Utility::copy(icosphere.attribute(Trade::MeshAttribute::Position), positions); /* Subdivide 5 times */ for(std::size_t i = 0; i != 5; ++i) - MeshTools::subdivide(icosphere.indices(), icosphere.positions(0), interpolator); + MeshTools::subdivide(indices, positions, interpolator); } } void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfter() { + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + CORRADE_BENCHMARK(3) { - Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); + Containers::Array indices; + arrayResize(indices, Containers::NoInit, icosphere.indexCount()); + Utility::copy(icosphere.indices(), indices); + + Containers::Array positions; + arrayResize(positions, Containers::NoInit, icosphere.vertexCount()); + Utility::copy(icosphere.attribute(Trade::MeshAttribute::Position), positions); /* Subdivide 5 times */ for(std::size_t i = 0; i != 5; ++i) - MeshTools::subdivide(icosphere.indices(), icosphere.positions(0), interpolator); + MeshTools::subdivide(indices, positions, interpolator); /* Remove duplicates after */ - icosphere.indices() = MeshTools::duplicate(icosphere.indices(), MeshTools::removeDuplicates(icosphere.positions(0))); + arrayResize(positions, MeshTools::removeDuplicatesIndexedInPlace( + stridedArrayView(indices), stridedArrayView(positions))); + } +} + +void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfterInPlace() { + CORRADE_BENCHMARK(3) { + /* Because that's what this thing does */ + Trade::MeshData icosphere = Primitives::icosphereSolid(5); } } void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesInBetween() { + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + CORRADE_BENCHMARK(3) { - Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); + Containers::Array indices; + arrayResize(indices, Containers::NoInit, icosphere.indexCount()); + Utility::copy(icosphere.indices(), indices); + + Containers::Array positions; + arrayResize(positions, Containers::NoInit, icosphere.vertexCount()); + Utility::copy(icosphere.attribute(Trade::MeshAttribute::Position), positions); /* Subdivide 5 times and remove duplicates during the operation */ for(std::size_t i = 0; i != 5; ++i) { - MeshTools::subdivide(icosphere.indices(), icosphere.positions(0), interpolator); - icosphere.indices() = MeshTools::duplicate(icosphere.indices(), MeshTools::removeDuplicates(icosphere.positions(0))); + MeshTools::subdivide(indices, positions, interpolator); + arrayResize(positions, MeshTools::removeDuplicatesIndexedInPlace( + stridedArrayView(indices), stridedArrayView(positions))); } } } diff --git a/src/Magnum/MeshTools/Test/SubdivideTest.cpp b/src/Magnum/MeshTools/Test/SubdivideTest.cpp index 34665591f7..f6f804a51c 100644 --- a/src/Magnum/MeshTools/Test/SubdivideTest.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideTest.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include "Magnum/MeshTools/RemoveDuplicates.h" @@ -35,8 +36,16 @@ namespace Magnum { namespace MeshTools { namespace Test { namespace { struct SubdivideTest: TestSuite::Tester { explicit SubdivideTest(); - void wrongIndexCount(); void subdivide(); + #ifdef MAGNUM_BUILD_DEPRECATED + void subdivideStl(); + #endif + void subdivideWrongIndexCount(); + template void subdivideInPlace(); + void subdivideInPlaceWrongIndexCount(); + void subdivideInPlaceSmallIndexType(); + + /* this is additionally regression-tested in PrimitivesIcosphereTest */ }; typedef Math::Vector<1, Int> Vector1; @@ -44,29 +53,94 @@ typedef Math::Vector<1, Int> Vector1; inline Vector1 interpolator(Vector1 a, Vector1 b) { return (a[0]+b[0])/2; } SubdivideTest::SubdivideTest() { - addTests({&SubdivideTest::wrongIndexCount, - &SubdivideTest::subdivide}); + addTests({&SubdivideTest::subdivide, + #ifdef MAGNUM_BUILD_DEPRECATED + &SubdivideTest::subdivideStl, + #endif + &SubdivideTest::subdivideWrongIndexCount, + &SubdivideTest::subdivideInPlace, + &SubdivideTest::subdivideInPlace, + &SubdivideTest::subdivideInPlace, + &SubdivideTest::subdivideInPlaceWrongIndexCount, + &SubdivideTest::subdivideInPlaceSmallIndexType}); } -void SubdivideTest::wrongIndexCount() { - std::stringstream ss; - Error redirectError{&ss}; - - std::vector positions; - std::vector indices{0, 1}; +void SubdivideTest::subdivide() { + auto positions = Containers::array({0, 2, 6, 8}); + auto indices = Containers::array({0, 1, 2, 1, 2, 3}); MeshTools::subdivide(indices, positions, interpolator); - CORRADE_COMPARE(ss.str(), "MeshTools::subdivide(): index count is not divisible by 3!\n"); + + CORRADE_COMPARE_AS(indices, Containers::arrayView({ + 4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(positions, Containers::arrayView({ + 0, 2, 6, 8, 1, 4, 3, 4, 7, 5 + }), TestSuite::Compare::Container); } -void SubdivideTest::subdivide() { +#ifdef MAGNUM_BUILD_DEPRECATED +void SubdivideTest::subdivideStl() { std::vector positions{0, 2, 6, 8}; std::vector indices{0, 1, 2, 1, 2, 3}; + CORRADE_IGNORE_DEPRECATED_PUSH + MeshTools::subdivide(indices, positions, interpolator); + CORRADE_IGNORE_DEPRECATED_POP + + CORRADE_COMPARE_AS(indices, + (std::vector{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(positions, + (std::vector{0, 2, 6, 8, 1, 4, 3, 4, 7, 5}), + TestSuite::Compare::Container); +} +#endif + +void SubdivideTest::subdivideWrongIndexCount() { + std::stringstream out; + Error redirectError{&out}; + + Containers::Array positions; + Containers::Array indices{2}; MeshTools::subdivide(indices, positions, interpolator); + CORRADE_COMPARE(out.str(), "MeshTools::subdivide(): index count is not divisible by 3\n"); +} + +template void SubdivideTest::subdivideInPlace() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[6*4]{0, 1, 2, 1, 2, 3, /* and 18 more */}; + Vector1 positions[4 + 6]{0, 2, 6, 8, /* and 6 more */}; + MeshTools::subdivideInPlace(Containers::stridedArrayView(indices), + Containers::stridedArrayView(positions), interpolator); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(positions), + Containers::arrayView({0, 2, 6, 8, 1, 4, 3, 4, 7, 5}), + TestSuite::Compare::Container); +} + +void SubdivideTest::subdivideInPlaceWrongIndexCount() { + std::stringstream out; + Error redirectError{&out}; + + UnsignedInt indices[6*4 + 1]{0, 1, 2, 1, 2, 3, /* and 18+1 more */}; + Vector1 positions[]{0}; + MeshTools::subdivideInPlace(Containers::stridedArrayView(indices), + Containers::stridedArrayView(positions), interpolator); + CORRADE_COMPARE(out.str(), "MeshTools::subdivideInPlace(): can't divide 25 indices to four parts with each having triangle faces\n"); +} - CORRADE_COMPARE(indices.size(), 24); +void SubdivideTest::subdivideInPlaceSmallIndexType() { + std::stringstream out; + Error redirectError{&out}; - CORRADE_VERIFY(positions == (std::vector{0, 2, 6, 8, 1, 4, 3, 4, 7, 5})); - CORRADE_COMPARE(indices, (std::vector{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3})); + UnsignedByte indices[6*4]{0, 1, 2, 1, 2, 3, /* and 18 more */}; + Vector1 positions[256]{}; + MeshTools::subdivideInPlace(Containers::stridedArrayView(indices), + Containers::stridedArrayView(positions), interpolator); + CORRADE_COMPARE(out.str(), "MeshTools::subdivideInPlace(): a 1-byte index type is too small for 256 vertices\n"); } }}}} diff --git a/src/Magnum/MeshTools/Test/TipsifyTest.cpp b/src/Magnum/MeshTools/Test/TipsifyTest.cpp index 3b7e972994..fbf3ae9e09 100644 --- a/src/Magnum/MeshTools/Test/TipsifyTest.cpp +++ b/src/Magnum/MeshTools/Test/TipsifyTest.cpp @@ -23,9 +23,12 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Magnum.h" +#include "Magnum/Math/TypeTraits.h" #include "Magnum/MeshTools/Tipsify.h" #include "Magnum/MeshTools/Implementation/Tipsify.h" @@ -35,7 +38,7 @@ struct TipsifyTest: TestSuite::Tester { explicit TipsifyTest(); void buildAdjacency(); - void tipsify(); + template void tipsify(); void oneDegenerateTriangle(); }; @@ -54,7 +57,7 @@ struct TipsifyTest: TestSuite::Tester { */ -const std::vector Indices{ +constexpr UnsignedInt Indices[]{ 4, 1, 0, 10, 9, 13, 6, 3, 2, @@ -83,32 +86,33 @@ constexpr std::size_t VertexCount = 19; TipsifyTest::TipsifyTest() { addTests({&TipsifyTest::buildAdjacency, - &TipsifyTest::tipsify, + &TipsifyTest::tipsify, + &TipsifyTest::tipsify, + &TipsifyTest::tipsify, &TipsifyTest::oneDegenerateTriangle}); } void TipsifyTest::buildAdjacency() { - std::vector indices = Indices; - std::vector liveTriangleCount, neighborOffset, neighbors; - Implementation::buildAdjacency(indices, VertexCount, liveTriangleCount, neighborOffset, neighbors); + Containers::Array liveTriangleCount, neighborOffset, neighbors; + Implementation::buildAdjacency(Containers::stridedArrayView(Indices), VertexCount, liveTriangleCount, neighborOffset, neighbors); - CORRADE_COMPARE(liveTriangleCount, (std::vector{ + CORRADE_COMPARE_AS(liveTriangleCount, Containers::arrayView({ 1, 3, 3, 2, 4, 6, 6, 2, 2, 6, 6, 4, 2, 3, 3, 1, 1, 1, 1 - })); + }), TestSuite::Compare::Container); - CORRADE_COMPARE(neighborOffset, (std::vector{ + CORRADE_COMPARE_AS(neighborOffset, Containers::arrayView({ 0, 1, 4, 7, 9, 13, 19, 25, 27, 29, 35, 41, 45, 47, 50, 53, 54, 55, 56, 57 - })); + }), TestSuite::Compare::Container); - CORRADE_COMPARE(neighbors, (std::vector{ + CORRADE_COMPARE_AS(neighbors, Containers::arrayView({ 0, 0, 7, 11, 2, 7, 13, @@ -130,14 +134,18 @@ void TipsifyTest::buildAdjacency() { 6, 18, 18, 18 - })); + }), TestSuite::Compare::Container); } -void TipsifyTest::tipsify() { - std::vector indices = Indices; - MeshTools::tipsify(indices, VertexCount, 3); +template void TipsifyTest::tipsify() { + setTestCaseTemplateName(Math::TypeTraits::name()); - CORRADE_COMPARE(indices, (std::vector{ + T indices[Containers::arraySize(Indices)]; + for(std::size_t i = 0; i != Containers::arraySize(Indices); ++i) + indices[i] = Indices[i]; + MeshTools::tipsifyInPlace(indices, VertexCount, 3); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), Containers::arrayView({ 4, 1, 0, 9, 5, 4, 1, 4, 5, @@ -157,16 +165,18 @@ void TipsifyTest::tipsify() { 2, 1, 5, 14, 15, 11, /* from dead-end vertex stack */ 16, 17, 18 /* arbitrary vertex */ - })); + }), TestSuite::Compare::Container); } void TipsifyTest::oneDegenerateTriangle() { /* There used to be an OOB access (neighbors[++ti]) caught by ASan, this triggers it */ - std::vector indices{0, 0, 0}; - MeshTools::tipsify(indices, 1, 2); + UnsignedInt indices[]{0, 0, 0}; + MeshTools::tipsifyInPlace(indices, 1, 2); - CORRADE_COMPARE(indices, (std::vector{0, 0, 0})); + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 0, 0}), + TestSuite::Compare::Container); } }}}} diff --git a/src/Magnum/MeshTools/Tipsify.cpp b/src/Magnum/MeshTools/Tipsify.cpp index b3d4373635..4182335181 100644 --- a/src/Magnum/MeshTools/Tipsify.cpp +++ b/src/Magnum/MeshTools/Tipsify.cpp @@ -25,36 +25,43 @@ #include "Tipsify.h" -#include +#include +#include #include "Magnum/MeshTools/Implementation/Tipsify.h" namespace Magnum { namespace MeshTools { -void tipsify(std::vector& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { +namespace { + +template void tipsifyInPlaceImplementation(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { /* Neighboring triangles for each vertex, per-vertex live triangle count */ - std::vector liveTriangleCount, neighborOffset, neighbors; - Implementation::buildAdjacency(indices, vertexCount, liveTriangleCount, neighborOffset, neighbors); + Containers::Array liveTriangleCount, neighborOffset, neighbors; + Implementation::buildAdjacency(indices, vertexCount, liveTriangleCount, neighborOffset, neighbors); /* Global time, per-vertex caching timestamps, per-triangle emmited flag */ UnsignedInt time = cacheSize+1; - std::vector timestamp(vertexCount); - std::vector emitted(indices.size()/3); + Containers::Array timestamp{vertexCount}; + /** @todo Have some bitset/staticbitset class for this */ + Containers::Array emitted{indices.size()/3}; /* Dead-end vertex stack */ - std::stack deadEndStack; + Containers::Array deadEndStack; /* Output index buffer */ - std::vector outputIndices; - outputIndices.reserve(indices.size()); + Containers::Array outputIndices{Containers::NoInit, indices.size()}; + std::size_t outputIndex = 0; + + /* Array with candidates for next fanning vertex (in 1-ring around + fanning vertex) */ + Containers::Array candidates; /* Starting vertex for fanning, cursor */ UnsignedInt fanningVertex = 0; UnsignedInt i = 0; while(fanningVertex != 0xFFFFFFFFu) { - /* Array with candidates for next fanning vertex (in 1-ring around - fanning vertex) */ - std::vector candidates; + /* Reset the candidates for this vertex */ + arrayResize(candidates, 0); /* For all neighbors of fanning vertex */ for(UnsignedInt ti = neighborOffset[fanningVertex]; ti != neighborOffset[fanningVertex+1]; ++ti) { @@ -68,12 +75,12 @@ void tipsify(std::vector& indices, const UnsignedInt vertexCount, c for(UnsignedInt vi = 0; vi != 3; ++vi) { const UnsignedInt v = indices[vi + t*3]; - outputIndices.push_back(v); + outputIndices[outputIndex++] = v; /* Add to dead end stack and candidates array */ /** @todo Limit size of dead end stack to cache size */ - deadEndStack.push(v); - candidates.push_back(v); + arrayAppend(deadEndStack, v); + arrayAppend(candidates, v); /* Decrease live triangle count */ --liveTriangleCount[v]; @@ -109,8 +116,8 @@ void tipsify(std::vector& indices, const UnsignedInt vertexCount, c if(fanningVertex == 0xFFFFFFFFu) { /* Find vertex with live triangles in dead-end stack */ while(!deadEndStack.empty()) { - unsigned int d = deadEndStack.top(); - deadEndStack.pop(); + UnsignedInt d = deadEndStack.back(); + arrayRemoveSuffix(deadEndStack); if(!liveTriangleCount[d]) continue; fanningVertex = d; @@ -129,8 +136,21 @@ void tipsify(std::vector& indices, const UnsignedInt vertexCount, c } /* Swap original index buffer with optimized */ - using std::swap; - swap(indices, outputIndices); + Utility::copy(outputIndices, indices); +} + +} + +void tipsifyInPlace(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { + tipsifyInPlaceImplementation(indices, vertexCount, cacheSize); +} + +void tipsifyInPlace(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { + tipsifyInPlaceImplementation(indices, vertexCount, cacheSize); +} + +void tipsifyInPlace(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { + tipsifyInPlaceImplementation(indices, vertexCount, cacheSize); } }} diff --git a/src/Magnum/MeshTools/Tipsify.h b/src/Magnum/MeshTools/Tipsify.h index 4855d175f3..54885a677d 100644 --- a/src/Magnum/MeshTools/Tipsify.h +++ b/src/Magnum/MeshTools/Tipsify.h @@ -26,30 +26,59 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::tipsify() + * @brief Function @ref Magnum::MeshTools::tipsifyInPlace() */ -#include +#include -#include "Magnum/Types.h" +#include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#include +#endif + namespace Magnum { namespace MeshTools { /** -@brief Tipsify the mesh +@brief Tipsify the mesh in-place @param[in,out] indices Indices array to operate on @param[in] vertexCount Vertex count @param[in] cacheSize Post-transform vertex cache size Optimizes the mesh for vertex-bound applications by rearranging its index array for beter usage of post-transform vertex cache. Algorithm used: -*Pedro V. Sander, Diego Nehab, and Joshua Barczak --- Fast Triangle Reordering +* *Pedro V. Sander, Diego Nehab, and Joshua Barczak --- Fast Triangle Reordering for Vertex Locality and Reduced Overdraw, SIGGRAPH 2007, http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/index.php*. @todo Ability to compute vertex count automatically */ -MAGNUM_MESHTOOLS_EXPORT void tipsify(std::vector& indices, UnsignedInt vertexCount, std::size_t cacheSize); +MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** + * @brief @copybrief tipsifyInPlace() + * @m_deprecated_since_latest Use @ref tipsifyInPlace() instead. + */ +inline CORRADE_DEPRECATED("use tipsifyInPlace() instead") void tipsify(std::vector& indices, UnsignedInt vertexCount, std::size_t cacheSize) { + tipsifyInPlace(indices, vertexCount, cacheSize); +} +#endif }} diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index 58c4d756bb..bfcb746858 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -61,7 +61,8 @@ For D3D, corresponds to @m_class{m-doc-external} [DXGI_FORMAT](https://docs.micr and import is provided by the @ref Trade::DdsImporter "DdsImporter" plugin; for Metal, corresponds to @m_class{m-doc-external} [MTLPixelFormat](https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc). See documentation of each value for more information about the mapping. -@see @ref pixelSize(), @ref CompressedPixelFormat, @ref Image, @ref ImageView +@see @ref pixelSize(), @ref CompressedPixelFormat, @ref Image, @ref ImageView, + @ref VertexFormat */ enum class PixelFormat: UnsignedInt { /* Zero reserved for an invalid format (but not being a named value) */ diff --git a/src/Magnum/Primitives/Axis.cpp b/src/Magnum/Primitives/Axis.cpp index 500a4ed097..e18f89351f 100644 --- a/src/Magnum/Primitives/Axis.cpp +++ b/src/Magnum/Primitives/Axis.cpp @@ -27,83 +27,99 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -using namespace Math::Literals; - -Trade::MeshData2D axis2D() { - return Trade::MeshData2D{MeshPrimitive::Lines, - {0, 1, - 1, 2, /* X axis */ - 1, 3, - - 4, 5, - 5, 6, /* Y axis */ - 5, 7}, - {{{ 0.0f, 0.0f}, - { 1.0f, 0.0f}, /* X axis */ - { 0.9f, 0.1f}, - { 0.9f, -0.1f}, - - { 0.0f, 0.0f}, - { 0.0f, 1.0f}, /* Y axis */ - { 0.1f, 0.9f}, - {-0.1f, 0.9f}}}, {}, - {{0xff0000_rgbf, - 0xff0000_rgbf, /* X axis */ - 0xff0000_rgbf, - 0xff0000_rgbf, - - 0x00ff00_rgbf, - 0x00ff00_rgbf, /* Y axis */ - 0x00ff00_rgbf, - 0x00ff00_rgbf}}}; +namespace { + +/* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ +constexpr UnsignedShort Indices2D[]{ + 0, 1, + 1, 2, /* X axis */ + 1, 3, + + 4, 5, + 5, 6, /* Y axis */ + 5, 7 +}; +constexpr UnsignedShort Indices3D[]{ + 0, 1, + 1, 2, /* X axis */ + 1, 3, + + 4, 5, + 5, 6, /* Y axis */ + 5, 7, + + 8, 9, + 9, 10, /* Z axis */ + 9, 11 +}; + +constexpr struct Vertex2D { + Vector2 position; + Color3 color; +} Vertices2D[]{ + {{ 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, /* X axis */ + {{ 0.9f, 0.1f}, {1.0f, 0.0f, 0.0f}}, + {{ 0.9f, -0.1f}, {1.0f, 0.0f, 0.0f}}, + + {{ 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{ 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}}, /* Y axis */ + {{ 0.1f, 0.9f}, {0.0f, 1.0f, 0.0f}}, + {{-0.1f, 0.9f}, {0.0f, 1.0f, 0.0f}} +}; +constexpr struct Vertex3D { + Vector3 position; + Color3 color; +} Vertices3D[]{ + {{ 0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, /* X axis */ + {{ 0.9f, 0.1f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{ 0.9f, -0.1f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + + {{ 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{ 0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, /* Y axis */ + {{ 0.1f, 0.9f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{-0.1f, 0.9f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + + {{ 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{ 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, /* Z axis */ + {{ 0.1f, 0.0f, 0.9f}, {0.0f, 0.0f, 1.0f}}, + {{-0.1f, 0.0f, 0.9f}, {0.0f, 0.0f, 1.0f}} +}; + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices2D, &Vertices2D[0].position, + Containers::arraySize(Vertices2D), sizeof(Vertex2D))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::stridedArrayView(Vertices2D, &Vertices2D[0].color, + Containers::arraySize(Vertices2D), sizeof(Vertex2D))} +}; +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices3D, &Vertices3D[0].position, + Containers::arraySize(Vertices3D), sizeof(Vertex3D))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::stridedArrayView(Vertices3D, &Vertices3D[0].color, + Containers::arraySize(Vertices3D), sizeof(Vertex3D))} +}; + +} + +Trade::MeshData axis2D() { + return Trade::MeshData{MeshPrimitive::Lines, + {}, Indices2D, Trade::MeshIndexData{Indices2D}, + {}, Vertices2D, Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } -Trade::MeshData3D axis3D() { - return Trade::MeshData3D{MeshPrimitive::Lines, - {0, 1, - 1, 2, /* X axis */ - 1, 3, - - 4, 5, - 5, 6, /* Y axis */ - 5, 7, - - 8, 9, - 9, 10, /* Z axis */ - 9, 11}, - {{{ 0.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, /* X axis */ - { 0.9f, 0.1f, 0.0f}, - { 0.9f, -0.1f, 0.0f}, - - { 0.0f, 0.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, /* Y axis */ - { 0.1f, 0.9f, 0.0f}, - {-0.1f, 0.9f, 0.0f}, - - { 0.0f, 0.0f, 0.0f}, - { 0.0f, 0.0f, 1.0f}, /* Z axis */ - { 0.1f, 0.0f, 0.9f}, - {-0.1f, 0.0f, 0.9f}}}, {}, {}, - {{0xff0000_rgbf, - 0xff0000_rgbf, /* X axis */ - 0xff0000_rgbf, - 0xff0000_rgbf, - - 0x00ff00_rgbf, - 0x00ff00_rgbf, /* Y axis */ - 0x00ff00_rgbf, - 0x00ff00_rgbf, - - 0x0000ff_rgbf, - 0x0000ff_rgbf, /* Z axis */ - 0x0000ff_rgbf, - 0x0000ff_rgbf}}}; +Trade::MeshData axis3D() { + return Trade::MeshData{MeshPrimitive::Lines, + {}, Indices3D, Trade::MeshIndexData{Indices3D}, + {}, Vertices3D, Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } }} diff --git a/src/Magnum/Primitives/Axis.h b/src/Magnum/Primitives/Axis.h index 169cd44761..5c25f65475 100644 --- a/src/Magnum/Primitives/Axis.h +++ b/src/Magnum/Primitives/Axis.h @@ -37,26 +37,30 @@ namespace Magnum { namespace Primitives { /** @brief 2D axis -Two color-coded arrows for visualizing orientation (XY is RG). Indexed -@ref MeshPrimitive::Lines with vertex colors. +Two color-coded arrows for visualizing orientation (XY is RG). +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedShort indices, +interleaved @ref VertexFormat::Vector2 positions and @ref VertexFormat::Vector3 +colors. The returned instance references data stored in constant memory. @image html primitives-axis2d.png width=256px @see @ref axis3D(), @ref crosshair2D(), @ref line2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D axis2D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData axis2D(); /** @brief 3D axis -Three color-coded arrows for visualizing orientation (XYZ is RGB). Indexed -@ref MeshPrimitive::Lines with vertex colors. +Three color-coded arrows for visualizing orientation (XYZ is RGB). +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedShort indices, +interleaved @ref VertexFormat::Vector3 positions and @ref VertexFormat::Vector3 +colors. The returned instance references data stored in constant memory. @image html primitives-axis3d.png width=256px @see @ref axis2D(), @ref crosshair3D(), @ref line3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D axis3D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData axis3D(); }} diff --git a/src/Magnum/Primitives/Capsule.cpp b/src/Magnum/Primitives/Capsule.cpp index 53ebed1af4..e52e088813 100644 --- a/src/Magnum/Primitives/Capsule.cpp +++ b/src/Magnum/Primitives/Capsule.cpp @@ -25,28 +25,29 @@ #include "Capsule.h" +#include + #include "Magnum/Math/Color.h" #include "Magnum/Math/Functions.h" #include "Magnum/Mesh.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D capsule2DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const Float halfLength) { +Trade::MeshData capsule2DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const Float halfLength) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1, "Primitives::capsule2DWireframe(): at least one hemisphere ring and one cylinder ring expected", - (Trade::MeshData2D{MeshPrimitive::Triangles, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); - std::vector positions; - positions.reserve(hemisphereRings*4+2+(cylinderRings-1)*2); + Containers::Array vertexData; + arrayReserve(vertexData, hemisphereRings*4+2+(cylinderRings-1)*2); const Rad angleIncrement(Constants::piHalf()/hemisphereRings); const Float cylinderIncrement = 2.0f*halfLength/cylinderRings; /* Bottom cap vertex */ - positions.emplace_back(0.0f, -halfLength-1.0f); + arrayAppend(vertexData, {0.0f, -halfLength-1.0f}); /* Bottom hemisphere */ for(UnsignedInt i = 0; i != hemisphereRings; ++i) { @@ -54,13 +55,13 @@ Trade::MeshData2D capsule2DWireframe(const UnsignedInt hemisphereRings, const Un const std::pair sincos = Math::sincos(angle); const Float x = sincos.first; const Float y = -sincos.second-halfLength; - positions.insert(positions.end(), {{-x, y}, {x, y}}); + arrayAppend(vertexData, {{-x, y}, {x, y}}); } - /* Cylinder (bottom and top vertices are done within caps */ + /* Cylinder (bottom and top vertices are done within caps) */ for(UnsignedInt i = 0; i != cylinderRings-1; ++i) { const Float y = (i+1)*cylinderIncrement-halfLength; - positions.insert(positions.end(), {{-1.0f, y}, {1.0f, y}}); + arrayAppend(vertexData, {{-1.0f, y}, {1.0f, y}}); } /* Top hemisphere */ @@ -69,35 +70,38 @@ Trade::MeshData2D capsule2DWireframe(const UnsignedInt hemisphereRings, const Un const std::pair sincos = Math::sincos(angle); const Float x = sincos.second; const Float y = sincos.first+halfLength; - positions.insert(positions.end(), {{-x, y}, {x, y}}); + arrayAppend(vertexData, {{-x, y}, {x, y}}); } /* Top cap vertex */ - positions.emplace_back(0.0f, halfLength+1.0f); + arrayAppend(vertexData, {0.0f, halfLength+1.0f}); - std::vector indices; - indices.reserve(hemisphereRings*8+cylinderRings*4); + Containers::Array indexData; + arrayReserve(indexData, hemisphereRings*8+cylinderRings*4); /* Bottom cap indices */ - indices.insert(indices.end(), {0, 1, 0, 2}); + arrayAppend(indexData, {0u, 1u, 0u, 2u}); /* Side indices */ for(UnsignedInt i = 0; i != cylinderRings+hemisphereRings*2-2; ++i) - indices.insert(indices.end(), {i*2+1, i*2+3, - i*2+2, i*2+4}); + arrayAppend(indexData, {i*2+1, i*2+3, i*2+2, i*2+4}); /* Top cap indices */ - indices.insert(indices.end(), - {UnsignedInt(positions.size())-3, UnsignedInt(positions.size())-1, - UnsignedInt(positions.size())-2, UnsignedInt(positions.size())-1}); - - return Trade::MeshData2D{MeshPrimitive::Lines, std::move(indices), {std::move(positions)}, {}, {}, nullptr}; + arrayAppend(indexData, + {UnsignedInt(vertexData.size())-3, UnsignedInt(vertexData.size())-1, + UnsignedInt(vertexData.size())-2, UnsignedInt(vertexData.size())-1}); + + Trade::MeshIndexData indices{indexData}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, Containers::arrayView(vertexData)}; + return Trade::MeshData{MeshPrimitive::Lines, + Containers::arrayAllocatorCast(std::move(indexData)), indices, + Containers::arrayAllocatorCast(std::move(vertexData)), {positions}}; } -Trade::MeshData3D capsule3DSolid(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength, const CapsuleTextureCoords textureCoords) { +Trade::MeshData capsule3DSolid(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength, const CapsuleTextureCoords textureCoords) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1 && segments >= 3, "Primitives::capsule3DSolid(): at least one hemisphere ring, one cylinder ring and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid capsule(segments, textureCoords == CapsuleTextureCoords::Generate ? Implementation::Spheroid::TextureCoords::Generate : @@ -130,10 +134,10 @@ Trade::MeshData3D capsule3DSolid(const UnsignedInt hemisphereRings, const Unsign return capsule.finalize(); } -Trade::MeshData3D capsule3DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength) { +Trade::MeshData capsule3DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1 && segments >= 4 && segments%4 == 0, "Primitives::capsule3DWireframe(): at least one hemisphere and cylinder ring and multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::WireframeSpheroid capsule(segments/4); diff --git a/src/Magnum/Primitives/Capsule.h b/src/Magnum/Primitives/Capsule.h index fbc53ffd36..b94e465a97 100644 --- a/src/Magnum/Primitives/Capsule.h +++ b/src/Magnum/Primitives/Capsule.h @@ -43,14 +43,15 @@ namespace Magnum { namespace Primitives { @param halfLength Half the length of cylinder part Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. -Indexed @ref MeshPrimitive::Lines. +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedInt indices and +@ref VertexFormat::Vector2 positions. @image html primitives-capsule2dwireframe.png width=256px @see @ref capsule3DSolid(), @ref capsule3DWireframe(), @ref circle2DWireframe(), @ref squareWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D capsule2DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule2DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, Float halfLength); /** @brief Whether to generate capsule texture coordinates @@ -74,20 +75,21 @@ enum class CapsuleTextureCoords: UnsignedByte { @param textureCoords Whether to generate texture coordinates Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. -Indexed @ref MeshPrimitive::Triangles with normals and optional 2D texture -coordinates. If texture coordinates are generated, vertices of one segment are -duplicated for texture wrapping. +@ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, +interleaved @ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 +normals and optional @ref VertexFormat::Vector2 texture coordinates. If texture +coordinates are generated, vertices of one segment are duplicated for texture +wrapping. @image html primitives-capsule3dsolid.png width=256px The capsule is by default created with radius set to @f$ 1.0 @f$. In order to get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set -@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all -@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using -@ref MeshTools::transformPointsInPlace(). +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all positions by +@f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). @see @ref capsule3DWireframe(), @ref capsule2DWireframe(), @ref cylinderSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D capsule3DSolid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength, CapsuleTextureCoords textureCoords = CapsuleTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule3DSolid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength, CapsuleTextureCoords textureCoords = CapsuleTextureCoords::DontGenerate); /** @brief Wireframe 3D capsule @@ -100,13 +102,14 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D capsule3DSolid(UnsignedInt hemisphere @param halfLength Half the length of cylinder part Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. -Indexed @ref MeshPrimitive::Lines. +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedInt indices and +@ref VertexFormat::Vector3 positions. @image html primitives-capsule3dwireframe.png width=256px @see @ref capsule2DWireframe(), @ref capsule3DSolid(), @ref cylinderSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D capsule3DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule3DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength); }} diff --git a/src/Magnum/Primitives/Circle.cpp b/src/Magnum/Primitives/Circle.cpp index d38df8ded9..9086eea9bd 100644 --- a/src/Magnum/Primitives/Circle.cpp +++ b/src/Magnum/Primitives/Circle.cpp @@ -28,112 +28,171 @@ #include "Magnum/Math/Functions.h" #include "Magnum/Math/Color.h" #include "Magnum/Mesh.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D circle2DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { - CORRADE_ASSERT(segments >= 3, "Primitives::circle2DSolid(): segments must be >= 3", - (Trade::MeshData2D{MeshPrimitive::TriangleFan, {}, {}, {}, {}, nullptr})); +namespace { - std::vector positions; - positions.reserve(segments + 2); +constexpr Trade::MeshAttributeData AttributeData2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector2, + 0, 0, sizeof(Vector2)} +}; - std::vector> textureCoordinates; - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.emplace_back(); +constexpr Trade::MeshAttributeData AttributeData2DTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector2, + 0, 0, 2*sizeof(Vector2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + sizeof(Vector2), 0, 2*sizeof(Vector2)} +}; - /* Central point */ - positions.emplace_back(); - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(0.5f, 0.5f); +} - /* Points on circle. The first/last point is here twice to close the circle - properly. */ +Trade::MeshData circle2DSolid(const UnsignedInt segments, const CircleTextureCoords textureCoords) { + CORRADE_ASSERT(segments >= 3, "Primitives::circle2DSolid(): segments must be >= 3", + (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); + + /* Allocate interleaved array for all vertex data */ + Containers::Array attributes; + if(textureCoords == CircleTextureCoords::Generate) + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData2DTextureCoords); + else + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData2D); + const std::size_t stride = attributes[0].stride(); + Containers::Array vertexData{stride*(segments + 2)}; + + /* Fill positions */ + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.begin()), + segments + 2, std::ptrdiff_t(stride)}; + positions[0] = {}; + /* Points on the circle. The first/last point is here twice to close the + circle properly. */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments + 1; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - Vector2 position{sincos.second, sincos.first}; - positions.emplace_back(position); + positions[i + 1] = {sincos.second, sincos.first}; + } - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(position*0.5f + Vector2{0.5f}); + /* Fill texture coords, if any */ + if(textureCoords == CircleTextureCoords::Generate) { + Containers::StridedArrayView1D textureCoords{vertexData, + reinterpret_cast(vertexData.begin() + sizeof(Vector2)), + positions.size(), std::ptrdiff_t(stride)}; + for(std::size_t i = 0; i != positions.size(); ++i) + textureCoords[i] = positions[i]*0.5f + Vector2{0.5f}; } - return Trade::MeshData2D{MeshPrimitive::TriangleFan, {}, {std::move(positions)}, std::move(textureCoordinates), {}, nullptr}; + return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes), UnsignedInt(positions.size())}; } -Trade::MeshData2D circle2DWireframe(const UnsignedInt segments) { +Trade::MeshData circle2DWireframe(const UnsignedInt segments) { CORRADE_ASSERT(segments >= 3, "Primitives::circle2DWireframe(): segments must be >= 3", - (Trade::MeshData2D{MeshPrimitive::LineLoop, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::LineLoop, 0})); - std::vector positions; - positions.reserve(segments); + Containers::Array vertexData{segments*sizeof(Vector2)}; + auto positions = Containers::arrayCast(vertexData); /* Points on circle */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - positions.emplace_back(sincos.second, sincos.first); + positions[i] = {sincos.second, sincos.first}; } - return Trade::MeshData2D{MeshPrimitive::LineLoop, {}, {std::move(positions)}, {}, {}, nullptr}; + return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(AttributeData2D), UnsignedInt(positions.size())}; } -Trade::MeshData3D circle3DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { - CORRADE_ASSERT(segments >= 3, "Primitives::circle3DSolid(): segments must be >= 3", - (Trade::MeshData3D{MeshPrimitive::TriangleFan, {}, {}, {}, {}, {}, nullptr})); +namespace { + +constexpr Trade::MeshAttributeData AttributeData3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, 2*sizeof(Vector3)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + sizeof(Vector3), 0, 2*sizeof(Vector3)} +}; + +constexpr Trade::MeshAttributeData AttributeData3DTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, 2*sizeof(Vector3) + sizeof(Vector2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + sizeof(Vector3), 0, 2*sizeof(Vector3) + sizeof(Vector2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + 2*sizeof(Vector3), 0, 2*sizeof(Vector3) + sizeof(Vector2)} +}; + +constexpr Trade::MeshAttributeData AttributeData3DWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; - std::vector positions; - positions.reserve(segments + 2); +} - std::vector> textureCoordinates; - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.emplace_back(); +Trade::MeshData circle3DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { + CORRADE_ASSERT(segments >= 3, "Primitives::circle3DSolid(): segments must be >= 3", + (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); - /* Central point */ - positions.emplace_back(); + /* Allocate interleaved array for all vertex data */ + Containers::Array attributes; if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(0.5f, 0.5f); - - /* Points on circle. The first/last point is here twice to close the circle - properly. */ + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3DTextureCoords); + else + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3D); + const std::size_t stride = attributes[0].stride(); + Containers::Array vertexData{stride*(segments + 2)}; + + /* Fill positions */ + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.begin()), + segments + 2, std::ptrdiff_t(stride)}; + positions[0] = {}; + /* Points on the circle. The first/last point is here twice to close the + circle properly. */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments + 1; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - Vector3 position{sincos.second, sincos.first, 0.0f}; - positions.emplace_back(position); - - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(position.xy()*0.5f + Vector2{0.5f}); + positions[i + 1] = {sincos.second, sincos.first, 0.0f}; } - /* Normals. All pointing in the same direction. */ - std::vector normals{segments + 2, Vector3::zAxis(1.0f)}; + /* Fill normals */ + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.begin() + sizeof(Vector3)), + segments + 2, std::ptrdiff_t(stride)}; + for(Vector3& normal: normals) normal = Vector3::zAxis(1.0f); + + /* Fill texture coords, if any */ + if(textureCoords == CircleTextureCoords::Generate) { + Containers::StridedArrayView1D textureCoords{vertexData, + reinterpret_cast(vertexData.begin() + 2*sizeof(Vector3)), + positions.size(), std::ptrdiff_t(stride)}; + for(std::size_t i = 0; i != positions.size(); ++i) + textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; + } - return Trade::MeshData3D{MeshPrimitive::TriangleFan, {}, {std::move(positions)}, {std::move(normals)}, std::move(textureCoordinates), {}, nullptr}; + return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes), UnsignedInt(positions.size())}; } -Trade::MeshData3D circle3DWireframe(const UnsignedInt segments) { +Trade::MeshData circle3DWireframe(const UnsignedInt segments) { CORRADE_ASSERT(segments >= 3, "Primitives::circle3DWireframe(): segments must be >= 3", - (Trade::MeshData3D{MeshPrimitive::LineLoop, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::LineLoop, 0})); - std::vector positions; - positions.reserve(segments); + Containers::Array vertexData{segments*sizeof(Vector3)}; + auto positions = Containers::arrayCast(vertexData); /* Points on circle */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - positions.emplace_back(sincos.second, sincos.first, 0.0f); + positions[i] = {sincos.second, sincos.first, 0.0f}; } - return Trade::MeshData3D{MeshPrimitive::LineLoop, {}, {std::move(positions)}, {}, {}, {}, nullptr}; + return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(AttributeData3DWireframe), UnsignedInt(positions.size())}; } }} diff --git a/src/Magnum/Primitives/Circle.h b/src/Magnum/Primitives/Circle.h index e8fcd8b3d0..6a0eb90f7a 100644 --- a/src/Magnum/Primitives/Circle.h +++ b/src/Magnum/Primitives/Circle.h @@ -51,26 +51,29 @@ enum class CircleTextureCoords: UnsignedByte { @cpp 3 @ce. @param textureCoords Whether to generate texture coordinates -Circle with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::TriangleFan. +Circle with radius @cpp 1.0f @ce. @ref MeshPrimitive::TriangleFan with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector2 +positions and optional @ref VertexFormat::Vector2 texture coordinates. @image html primitives-circle2dsolid.png width=256px @see @ref circle2DWireframe(), @ref circle3DSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D circle2DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle2DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); /** @brief Wireframe 2D circle @param segments Number of segments. Must be greater or equal to @cpp 3 @ce. -Circle with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::LineLoop. +Circle with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::LineLoop with +@ref VertexFormat::Vector2 positions. @image html primitives-circle2dwireframe.png width=256px @see @ref circle2DSolid(), @ref circle3DWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D circle2DWireframe(UnsignedInt segments); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle2DWireframe(UnsignedInt segments); /** @brief Solid 3D circle @@ -79,26 +82,27 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D circle2DWireframe(UnsignedInt segment @param textureCoords Whether to generate texture coordinates Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed -@ref MeshPrimitive::TriangleFan with normals in positive Z direction. +@ref MeshPrimitive::TriangleFan with interleaved @ref VertexFormat::Vector3 +positions, @ref VertexFormat::Vector3 normals in positive Z direction and +optional @ref VertexFormat::Vector2 texture coordinates. @image html primitives-circle3dsolid.png width=256px @see @ref circle3DWireframe(), @ref circle2DSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D circle3DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle3DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); /** @brief Wireframe 3D circle @param segments Number of segments. Must be greater or equal to @cpp 3 @ce. -Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed -@ref MeshPrimitive::LineLoop. +Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::LineLoop with @ref VertexFormat::Vector2 positions. @image html primitives-circle3dwireframe.png width=256px @see @ref circle2DSolid(), @ref circle3DWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D circle3DWireframe(UnsignedInt segments); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle3DWireframe(UnsignedInt segments); }} diff --git a/src/Magnum/Primitives/Cone.cpp b/src/Magnum/Primitives/Cone.cpp index 856b6c9769..af573eec66 100644 --- a/src/Magnum/Primitives/Cone.cpp +++ b/src/Magnum/Primitives/Cone.cpp @@ -29,14 +29,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D coneSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const ConeFlags flags) { +Trade::MeshData coneSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const ConeFlags flags) { CORRADE_ASSERT(rings >= 1 && segments >= 3, "Primitives::coneSolid(): at least one ring and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid cone{segments, flags & ConeFlag::GenerateTextureCoords ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate}; @@ -64,10 +64,10 @@ Trade::MeshData3D coneSolid(const UnsignedInt rings, const UnsignedInt segments, return cone.finalize(); } -Trade::MeshData3D coneWireframe(const UnsignedInt segments, const Float halfLength) { +Trade::MeshData coneWireframe(const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(segments >= 4 && segments%4 == 0, "Primitives::coneWireframe(): multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Lines, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Lines, 0})); Implementation::WireframeSpheroid cone{segments/4}; cone.ring(-halfLength); diff --git a/src/Magnum/Primitives/Cone.h b/src/Magnum/Primitives/Cone.h index dcaab335bc..9c147b8b9b 100644 --- a/src/Magnum/Primitives/Cone.h +++ b/src/Magnum/Primitives/Cone.h @@ -65,21 +65,22 @@ CORRADE_ENUMSET_OPERATORS(ConeFlags) @param halfLength Half the cone length @param flags Flags -Cone along Y axis of radius @cpp 1.0f @ce. Indexed -@ref MeshPrimitive::Triangles. Note that in order to have properly smooth -normals over the whole area, the tip consists of @cpp segments*2 @ce vertices -instead of just one. +Cone along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions, @ref VertexFormat::Vector3 normals and optional +@ref VertexFormat::Vector2 texture coordinates. Note that in order to have +properly smooth normals over the whole area, the tip consists of +@cpp segments*2 @ce vertices instead of just one. @image html primitives-conesolid.png width=256px The cone is by default created with radius set to @f$ 1.0 @f$. In order to get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set -@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all -@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using -@ref MeshTools::transformPointsInPlace(). +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all positions by +@f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). @see @ref coneWireframe(), @ref cylinderSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, ConeFlags flags = {}); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData coneSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, ConeFlags flags = {}); /** @brief Wireframe 3D cone @@ -87,13 +88,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneSolid(UnsignedInt rings, Unsigned @cpp 4 @ce and multiple of @cpp 4 @ce. @param halfLength Half the cone length -Cone along Y axis of radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Lines. +Cone along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-conewireframe.png width=256px @see @ref coneSolid(), @ref cylinderWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneWireframe(UnsignedInt segments, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData coneWireframe(UnsignedInt segments, Float halfLength); }} diff --git a/src/Magnum/Primitives/Crosshair.cpp b/src/Magnum/Primitives/Crosshair.cpp index eed1fefe1e..1795f5117a 100644 --- a/src/Magnum/Primitives/Crosshair.cpp +++ b/src/Magnum/Primitives/Crosshair.cpp @@ -27,24 +27,57 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D crosshair2D() { - return Trade::MeshData2D{MeshPrimitive::Lines, {}, {{ - {-1.0f, 0.0f}, {1.0f, 0.0f}, - { 0.0f, -1.0f}, {0.0f, 1.0f} - }}, {}, {}, nullptr}; +namespace { + +/* Can't be just an array of Vector2 but has to be a struct, because then MSVC + 2015 fails with an assertion like + + Trade::MeshData: attribute 0 [0x7ffd6c4fb290:0x7ffd6c4fb338] is not contained in passed vertexData array [0x7ffd6c4fb170:0x7ffd6c4fb218] + + which leads me to believe that it will make two copies of the data, one for + the MeshAttributeData constructor and one for the MeshData constructor. Same + for the Cube, Square and Plane primitives. Interestingly enough, this isn't + a problem for index arrays (maybe because those are integers and not + constexpr classes?). Also not a problem for MSVC 2017 and up. */ +constexpr struct Vertex2D { + Vector2 position; +} Vertices2D[]{ + {{-1.0f, 0.0f}}, {{1.0f, 0.0f}}, + {{ 0.0f, -1.0f}}, {{0.0f, 1.0f}} +}; +constexpr struct Vertex3D { + Vector3 position; +} Vertices3D[]{ + {{-1.0f, 0.0f, 0.0f}}, {{1.0f, 0.0f, 0.0f}}, + {{ 0.0f, -1.0f, 0.0f}}, {{0.0f, 1.0f, 0.0f}}, + {{ 0.0f, 0.0f, -1.0f}}, {{0.0f, 0.0f, 1.0f}} +}; + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices2D, &Vertices2D[0].position, + Containers::arraySize(Vertices2D), sizeof(Vertex2D))} +}; +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices3D, &Vertices3D[0].position, + Containers::arraySize(Vertices3D), sizeof(Vertex3D))} +}; + +} + +Trade::MeshData crosshair2D() { + return Trade::MeshData{MeshPrimitive::Lines, {}, Vertices2D, + Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } -Trade::MeshData3D crosshair3D() { - return Trade::MeshData3D{MeshPrimitive::Lines, {}, {{ - {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, - { 0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f} - }}, {}, {}, {}, nullptr}; +Trade::MeshData crosshair3D() { + return Trade::MeshData{MeshPrimitive::Lines, {}, Vertices3D, + Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } }} diff --git a/src/Magnum/Primitives/Crosshair.h b/src/Magnum/Primitives/Crosshair.h index 72151394d2..873dda2eb8 100644 --- a/src/Magnum/Primitives/Crosshair.h +++ b/src/Magnum/Primitives/Crosshair.h @@ -37,24 +37,28 @@ namespace Magnum { namespace Primitives { /** @brief 2D crosshair -2x2 crosshair (two crossed lines), non-indexed @ref MeshPrimitive::Lines. +2x2 crosshair (two crossed lines). Non-indexed @ref MeshPrimitive::Lines with +@ref VertexFormat::Vector2 positions. The returned instance references data +stored in constant memory. @image html primitives-crosshair2d.png width=256px @see @ref crosshair3D(), @ref axis2D(), @ref line2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D crosshair2D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData crosshair2D(); /** @brief 3D crosshair -2x2x2 crosshair (three crossed lines), non-indexed @ref MeshPrimitive::Lines. +2x2x2 crosshair (three crossed lines). Non-indexed @ref MeshPrimitive::Lines +with @ref VertexFormat::Vector3 positions. The returned instance references +data stored in constant memory. @image html primitives-crosshair3d.png width=256px @see @ref crosshair2D(), @ref axis2D(), @ref line3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D crosshair3D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData crosshair3D(); }} diff --git a/src/Magnum/Primitives/Cube.cpp b/src/Magnum/Primitives/Cube.cpp index b5d8c4716d..d22f0e08c1 100644 --- a/src/Magnum/Primitives/Cube.cpp +++ b/src/Magnum/Primitives/Cube.cpp @@ -27,82 +27,79 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D cubeSolid() { - return Trade::MeshData3D{MeshPrimitive::Triangles, { - 0, 1, 2, 0, 2, 3, /* +Z */ - 4, 5, 6, 4, 6, 7, /* +X */ - 8, 9, 10, 8, 10, 11, /* +Y */ - 12, 13, 14, 12, 14, 15, /* -Z */ - 16, 17, 18, 16, 18, 19, /* -Y */ - 20, 21, 22, 20, 22, 23 /* -X */ - }, {{ - {-1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, /* +Z */ - {-1.0f, 1.0f, 1.0f}, - - { 1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, /* +X */ - { 1.0f, 1.0f, 1.0f}, - - {-1.0f, 1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, - { 1.0f, 1.0f, -1.0f}, /* +Y */ - {-1.0f, 1.0f, -1.0f}, - - { 1.0f, -1.0f, -1.0f}, - {-1.0f, -1.0f, -1.0f}, - {-1.0f, 1.0f, -1.0f}, /* -Z */ - { 1.0f, 1.0f, -1.0f}, - - {-1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, 1.0f}, /* -Y */ - {-1.0f, -1.0f, 1.0f}, - - {-1.0f, -1.0f, -1.0f}, - {-1.0f, -1.0f, 1.0f}, - {-1.0f, 1.0f, 1.0f}, /* -X */ - {-1.0f, 1.0f, -1.0f} - }}, {{ - { 0.0f, 0.0f, 1.0f}, - { 0.0f, 0.0f, 1.0f}, - { 0.0f, 0.0f, 1.0f}, /* +Z */ - { 0.0f, 0.0f, 1.0f}, - - { 1.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, /* +X */ - { 1.0f, 0.0f, 0.0f}, - - { 0.0f, 1.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, /* +Y */ - { 0.0f, 1.0f, 0.0f}, - - { 0.0f, 0.0f, -1.0f}, - { 0.0f, 0.0f, -1.0f}, - { 0.0f, 0.0f, -1.0f}, /* -Z */ - { 0.0f, 0.0f, -1.0f}, - - { 0.0f, -1.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, /* -Y */ - { 0.0f, -1.0f, 0.0f}, - - {-1.0f, 0.0f, 0.0f}, - {-1.0f, 0.0f, 0.0f}, - {-1.0f, 0.0f, 0.0f}, /* -X */ - {-1.0f, 0.0f, 0.0f} - }}, {}, {}, nullptr}; +namespace { + +/* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ +constexpr UnsignedShort IndicesSolid[]{ + 0, 1, 2, 0, 2, 3, /* +Z */ + 4, 5, 6, 4, 6, 7, /* +X */ + 8, 9, 10, 8, 10, 11, /* +Y */ + 12, 13, 14, 12, 14, 15, /* -Z */ + 16, 17, 18, 16, 18, 19, /* -Y */ + 20, 21, 22, 20, 22, 23 /* -X */ +}; +constexpr struct VertexSolid { + Vector3 position; + Vector3 normal; +} VerticesSolid[]{ + {{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, + {{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, /* +Z */ + {{-1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, + + {{ 1.0f, -1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, + {{ 1.0f, -1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, /* +X */ + {{ 1.0f, 1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, + + {{-1.0f, 1.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}, + {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, /* +Y */ + {{-1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, + + {{ 1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, + {{-1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, + {{-1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, /* -Z */ + {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, + + {{-1.0f, -1.0f, -1.0f}, { 0.0f, -1.0f, 0.0f}}, + {{ 1.0f, -1.0f, -1.0f}, { 0.0f, -1.0f, 0.0f}}, + {{ 1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, /* -Y */ + {{-1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, + + {{-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, + {{-1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, + {{-1.0f, 1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, /* -X */ + {{-1.0f, 1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}} +}; +constexpr Trade::MeshAttributeData AttributesSolid[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].normal, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))} +}; + +} + +Trade::MeshData cubeSolid() { + return Trade::MeshData{MeshPrimitive::Triangles, + {}, IndicesSolid, Trade::MeshIndexData{IndicesSolid}, + {}, VerticesSolid, Trade::meshAttributeDataNonOwningArray(AttributesSolid)}; } -Trade::MeshData3D cubeSolidStrip() { +namespace { + +/* Can't be just an array of Vector3 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexSolidStrip { + Vector3 position; +} VerticesSolidStrip[]{ /* Sources: https://twitter.com/Donzanoid/status/436843034966507520 http://www.asmcommunity.net/forums/topic/?id=6284#post-45209 @@ -126,41 +123,70 @@ Trade::MeshData3D cubeSolidStrip() { |F \| 2---3 */ - return Trade::MeshData3D{MeshPrimitive::TriangleStrip, {}, {{ - { 1.0f, 1.0f, 1.0f}, /* 3 */ - {-1.0f, 1.0f, 1.0f}, /* 2 */ - { 1.0f, -1.0f, 1.0f}, /* 6 */ - {-1.0f, -1.0f, 1.0f}, /* 7 */ - {-1.0f, -1.0f, -1.0f}, /* 4 */ - {-1.0f, 1.0f, 1.0f}, /* 2 */ - {-1.0f, 1.0f, -1.0f}, /* 0 */ - { 1.0f, 1.0f, 1.0f}, /* 3 */ - { 1.0f, 1.0f, -1.0f}, /* 1 */ - { 1.0f, -1.0f, 1.0f}, /* 6 */ - { 1.0f, -1.0f, -1.0f}, /* 5 */ - {-1.0f, -1.0f, -1.0f}, /* 4 */ - { 1.0f, 1.0f, -1.0f}, /* 1 */ - {-1.0f, 1.0f, -1.0f} /* 0 */ - }}, {}, {}, {}, nullptr}; + {{ 1.0f, 1.0f, 1.0f}}, /* 3 */ + {{-1.0f, 1.0f, 1.0f}}, /* 2 */ + {{ 1.0f, -1.0f, 1.0f}}, /* 6 */ + {{-1.0f, -1.0f, 1.0f}}, /* 7 */ + {{-1.0f, -1.0f, -1.0f}}, /* 4 */ + {{-1.0f, 1.0f, 1.0f}}, /* 2 */ + {{-1.0f, 1.0f, -1.0f}}, /* 0 */ + {{ 1.0f, 1.0f, 1.0f}}, /* 3 */ + {{ 1.0f, 1.0f, -1.0f}}, /* 1 */ + {{ 1.0f, -1.0f, 1.0f}}, /* 6 */ + {{ 1.0f, -1.0f, -1.0f}}, /* 5 */ + {{-1.0f, -1.0f, -1.0f}}, /* 4 */ + {{ 1.0f, 1.0f, -1.0f}}, /* 1 */ + {{-1.0f, 1.0f, -1.0f}} /* 0 */ +}; +constexpr Trade::MeshAttributeData AttributesSolidStrip[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolidStrip, &VerticesSolidStrip[0].position, + Containers::arraySize(VerticesSolidStrip), sizeof(VertexSolidStrip))} +}; + +} + +Trade::MeshData cubeSolidStrip() { + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolidStrip, Trade::meshAttributeDataNonOwningArray(AttributesSolidStrip)}; +} + +namespace { + +/* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ +constexpr UnsignedShort IndicesWireframe[]{ + 0, 1, 1, 2, 2, 3, 3, 0, /* +Z */ + 4, 5, 5, 6, 6, 7, 7, 4, /* -Z */ + 1, 5, 2, 6, /* +X */ + 0, 4, 3, 7 /* -X */ +}; +/* Can't be just an array of Vector3 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexWireframe { + Vector3 position; +} VerticesWireframe[]{ + {{-1.0f, -1.0f, 1.0f}}, + {{ 1.0f, -1.0f, 1.0f}}, + {{ 1.0f, 1.0f, 1.0f}}, + {{-1.0f, 1.0f, 1.0f}}, + + {{-1.0f, -1.0f, -1.0f}}, + {{ 1.0f, -1.0f, -1.0f}}, + {{ 1.0f, 1.0f, -1.0f}}, + {{-1.0f, 1.0f, -1.0f}} +}; +constexpr Trade::MeshAttributeData AttributesWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesWireframe, &VerticesWireframe[0].position, + Containers::arraySize(VerticesWireframe), sizeof(VertexWireframe))} +}; + } -Trade::MeshData3D cubeWireframe() { - return Trade::MeshData3D{MeshPrimitive::Lines, { - 0, 1, 1, 2, 2, 3, 3, 0, /* +Z */ - 4, 5, 5, 6, 6, 7, 7, 4, /* -Z */ - 1, 5, 2, 6, /* +X */ - 0, 4, 3, 7 /* -X */ - }, {{ - {-1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, - {-1.0f, 1.0f, 1.0f}, - - {-1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, - {-1.0f, 1.0f, -1.0f} - }}, {}, {}, {}, nullptr}; +Trade::MeshData cubeWireframe() { + return Trade::MeshData{MeshPrimitive::Lines, + {}, IndicesWireframe, Trade::MeshIndexData{IndicesWireframe}, + {}, VerticesWireframe, Trade::meshAttributeDataNonOwningArray(AttributesWireframe)}; } }} diff --git a/src/Magnum/Primitives/Cube.h b/src/Magnum/Primitives/Cube.h index 4e31f7f69b..14e42e266a 100644 --- a/src/Magnum/Primitives/Cube.h +++ b/src/Magnum/Primitives/Cube.h @@ -37,36 +37,42 @@ namespace Magnum { namespace Primitives { /** @brief Solid 3D cube -Indexed @ref MeshPrimitive::Triangles with flat normals. +@ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedShort indices, +interleaved @ref VertexFormat::Vector3 positions and flat +@ref VertexFormat::Vector3 normals. The returned instance references data +stored in constant memory. @image html primitives-cubesolid.png width=256px @see @ref cubeSolidStrip(), @ref cubeWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cubeSolid(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeSolid(); /** @brief Solid 3D cube as a single strip -Non-indexed @ref MeshPrimitive::TriangleStrip. Just positions, no -normals or anything else. +Non-indexed @ref MeshPrimitive::TriangleStrip with @ref VertexFormat::Vector3 +positions. The returned instance references data stored in constant memory. No +normals or anything else --- use @ref cubeSolid() instead if you need these. @image html primitives-cubesolid.png width=256px -@see @ref cubeSolid(), @ref cubeWireframe() +@see @ref cubeWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cubeSolidStrip(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeSolidStrip(); /** @brief Wireframe 3D cube -Indexed @ref MeshPrimitive::Lines. +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedShort indices and +@ref VertexFormat::Vector3 positions. The returned instance references data +stored in constant memory. @image html primitives-cubewireframe.png width=256px @see @ref cubeSolid(), @ref cubeSolidStrip() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cubeWireframe(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeWireframe(); }} diff --git a/src/Magnum/Primitives/Cylinder.cpp b/src/Magnum/Primitives/Cylinder.cpp index 640f91f48b..060c1bce48 100644 --- a/src/Magnum/Primitives/Cylinder.cpp +++ b/src/Magnum/Primitives/Cylinder.cpp @@ -29,14 +29,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D cylinderSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const CylinderFlags flags) { +Trade::MeshData cylinderSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const CylinderFlags flags) { CORRADE_ASSERT(rings >= 1 && segments >= 3, "Primitives::cylinderSolid(): at least one ring and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid cylinder(segments, flags & CylinderFlag::GenerateTextureCoords ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate); @@ -71,10 +71,10 @@ Trade::MeshData3D cylinderSolid(const UnsignedInt rings, const UnsignedInt segme return cylinder.finalize(); } -Trade::MeshData3D cylinderWireframe(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength) { +Trade::MeshData cylinderWireframe(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(rings >= 1 && segments >= 4 && segments%4 == 0, "Primitives::cylinderWireframe(): at least one ring and multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Lines, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Lines, 0})); Implementation::WireframeSpheroid cylinder(segments/4); diff --git a/src/Magnum/Primitives/Cylinder.h b/src/Magnum/Primitives/Cylinder.h index 70a408028c..0a57b95ba8 100644 --- a/src/Magnum/Primitives/Cylinder.h +++ b/src/Magnum/Primitives/Cylinder.h @@ -65,21 +65,22 @@ CORRADE_ENUMSET_OPERATORS(CylinderFlags) @param halfLength Half the cylinder length @param flags Flags -Cylinder along Y axis of radius @cpp 1.0f @ce. Indexed -@ref MeshPrimitive::Triangles with normals, optional 2D texture coordinates and -optional capped ends. If texture coordinates are generated, vertices of one -segment are duplicated for texture wrapping. +Cylinder along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles +with @ref MeshIndexType::UnsignedInt indices, interleaved +@ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 normals, +optional @ref VertexFormat::Vector2 texture coordinates and optional capped +ends. If texture coordinates are generated, vertices of one segment are +duplicated for texture wrapping. @image html primitives-cylindersolid.png width=256px The cylinder is by default created with radius set to @f$ 1.0 @f$. In order to get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set -@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all -@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using -@ref MeshTools::transformPointsInPlace(). +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all positions by +@f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). @see @ref cylinderWireframe(), @ref coneSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, CylinderFlags flags = {}); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cylinderSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, CylinderFlags flags = {}); /** @brief Wireframe 3D cylinder @@ -89,14 +90,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderSolid(UnsignedInt rings, Unsi @cpp 4 @ce and multiple of @cpp 4 @ce. @param halfLength Half the cylinder length -Cylinder along Y axis of radius @cpp 1.0f @ce. Indexed -@ref MeshPrimitive::Lines. +Cylinder along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-cylinderwireframe.png width=256px @see @ref cylinderSolid(), @ref coneWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderWireframe(UnsignedInt rings, UnsignedInt segments, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cylinderWireframe(UnsignedInt rings, UnsignedInt segments, Float halfLength); }} diff --git a/src/Magnum/Primitives/Gradient.cpp b/src/Magnum/Primitives/Gradient.cpp index 074ef78d78..4e2c961d90 100644 --- a/src/Magnum/Primitives/Gradient.cpp +++ b/src/Magnum/Primitives/Gradient.cpp @@ -28,17 +28,35 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Intersection.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB) { - std::vector positions{Vector2{ 1.0f, -1.0f}, - Vector2{ 1.0f, 1.0f}, - Vector2{-1.0f, -1.0f}, - Vector2{-1.0f, 1.0f}}; - std::vector colors{4}; +namespace { + +struct Vertex2D { + Vector2 position; + Color4 color; +}; + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, offsetof(Vertex2D, position), + 4, sizeof(Vertex2D)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector4, offsetof(Vertex2D, color), + 4, sizeof(Vertex2D)}, +}; + +} + +Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB) { + Containers::Array vertexData{sizeof(Vertex2D)*4}; + auto vertices = Containers::arrayCast(vertexData); + vertices[0].position = { 1.0f, -1.0f}; + vertices[1].position = { 1.0f, 1.0f}; + vertices[2].position = {-1.0f, -1.0f}; + vertices[3].position = {-1.0f, 1.0f}; /* For every corner, take a line perpendicular to the gradient direction and passing through the corner. The calculated intersection position @@ -47,27 +65,55 @@ Trade::MeshData2D gradient2D(const Vector2& a, const Color4& colorA, const Vecto const Vector2 direction = b - a; const Vector2 perpendicular = direction.perpendicular(); for(std::size_t i = 0; i != 4; ++i) { - const Float t = Math::Intersection::lineSegmentLine(a, direction, positions[i], perpendicular); - colors[i] = Math::lerp(colorA, colorB, t); + const Float t = Math::Intersection::lineSegmentLine(a, direction, vertices[i].position, perpendicular); + vertices[i].color = Math::lerp(colorA, colorB, t); } - return Trade::MeshData2D{MeshPrimitive::TriangleStrip, {}, {std::move(positions)}, {}, {std::move(colors)}, nullptr}; + return Trade::MeshData{MeshPrimitive::TriangleStrip, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } -Trade::MeshData2D gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight) { +Trade::MeshData gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight) { return Primitives::gradient2D({-1.0f, 0.0f}, colorLeft, {1.0f, 0.0f}, colorRight); } -Trade::MeshData2D gradient2DVertical(const Color4& colorBottom, const Color4& colorTop) { +Trade::MeshData gradient2DVertical(const Color4& colorBottom, const Color4& colorTop) { return Primitives::gradient2D({0.0f, -1.0f}, colorBottom, {0.0f, 1.0f}, colorTop); } -Trade::MeshData3D gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB) { - std::vector positions{Vector3{ 1.0f, -1.0f, 0.0f}, - Vector3{ 1.0f, 1.0f, 0.0f}, - Vector3{-1.0f, -1.0f, 0.0f}, - Vector3{-1.0f, 1.0f, 0.0f}}; - std::vector colors{4}; +namespace { + +struct Vertex3D { + Vector3 position; + Vector3 normal; + Color4 color; +}; + +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, offsetof(Vertex3D, position), + 4, sizeof(Vertex3D)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, offsetof(Vertex3D, normal), + 4, sizeof(Vertex3D)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector4, offsetof(Vertex3D, color), + 4, sizeof(Vertex3D)}, +}; + +} + +Trade::MeshData gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB) { + Containers::Array vertexData{sizeof(Vertex3D)*4}; + auto vertices = Containers::arrayCast(vertexData); + vertices[0].position = { 1.0f, -1.0f, 0}; + vertices[1].position = { 1.0f, 1.0f, 0}; + vertices[2].position = {-1.0f, -1.0f, 0}; + vertices[3].position = {-1.0f, 1.0f, 0}; + vertices[0].normal = {0.0f, 0.0f, 1.0f}; + vertices[1].normal = {0.0f, 0.0f, 1.0f}; + vertices[2].normal = {0.0f, 0.0f, 1.0f}; + vertices[3].normal = {0.0f, 0.0f, 1.0f}; /* For every corner, take a plane perpendicular to the gradient direction and passing through the corner. The calculated intersection position @@ -75,24 +121,20 @@ Trade::MeshData3D gradient3D(const Vector3& a, const Color4& colorA, const Vecto for given corner. */ const Vector3 direction = b - a; for(std::size_t i = 0; i != 4; ++i) { - const Vector4 plane = Math::planeEquation(direction, positions[i]); + const Vector4 plane = Math::planeEquation(direction, vertices[i].position); const Float t = Math::Intersection::planeLine(plane, a, direction); - colors[i] = Math::lerp(colorA, colorB, t); + vertices[i].color = Math::lerp(colorA, colorB, t); } - return Trade::MeshData3D{MeshPrimitive::TriangleStrip, {}, {std::move(positions)}, {{ - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f} - }}, {}, {std::move(colors)}, nullptr}; + return Trade::MeshData{MeshPrimitive::TriangleStrip, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } -Trade::MeshData3D gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight) { +Trade::MeshData gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight) { return Primitives::gradient3D({-1.0f, 0.0f, 0.0f}, colorLeft, {1.0f, 0.0f, 0.0f}, colorRight); } -Trade::MeshData3D gradient3DVertical(const Color4& colorBottom, const Color4& colorTop) { +Trade::MeshData gradient3DVertical(const Color4& colorBottom, const Color4& colorTop) { return Primitives::gradient3D({0.0f, -1.0f, 0.0f}, colorBottom, {0.0f, 1.0f, 0.0f}, colorTop); } diff --git a/src/Magnum/Primitives/Gradient.h b/src/Magnum/Primitives/Gradient.h index 0a8780ed45..b391603344 100644 --- a/src/Magnum/Primitives/Gradient.h +++ b/src/Magnum/Primitives/Gradient.h @@ -38,16 +38,18 @@ namespace Magnum { namespace Primitives { /** @brief 2D square with a gradient -2x2 square with vertex colors. Non-indexed @ref MeshPrimitive::TriangleStrip. -Vertex colors correspond to the gradient defined by the endpoints @p a and -@p b, linearly interpolated from @p colorA to @p colorB. +2x2 square with vertex colors. Non-indexed @ref MeshPrimitive::TriangleStrip +with interleaved @ref VertexFormat::Vector2 positions and +@ref VertexFormat::Vector4 colors. Vertex colors correspond to the gradient +defined by the endpoints @p a and @p b, linearly interpolated from @p colorA to +@p colorB. @image html primitives-gradient2d.png width=256px @see @ref gradient2DHorizontal(), @ref gradient2DVertical(), @ref gradient3D(), @ref squareSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB); /** @brief 2D square with a horizontal gradient @@ -60,7 +62,7 @@ Equivalent to calling @ref gradient2D() like this: @see @ref gradient2DVertical(), @ref gradient3DHorizontal(), @ref squareSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight); /** @brief 2D square with a vertical gradient @@ -73,22 +75,24 @@ Equivalent to calling @ref gradient2D() like this: @see @ref gradient2DHorizontal(), @ref gradient3DVertical(), @ref squareSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D gradient2DVertical(const Color4& colorBottom, const Color4& colorTop); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient2DVertical(const Color4& colorBottom, const Color4& colorTop); /** @brief 3D plane with a gradient 2x2 plane with vertex colors. Non-indexed @ref MeshPrimitive::TriangleStrip on -the XY plane with normals in positive Z direction. Vertex colors correspond to -the gradient defined by the endpoints @p a and @p b, linearly interpolated from -@p colorA to @p colorB. +the XY plane with interleaved @ref VertexFormat::Vector3 positions, +@ref VertexFormat::Vector3 normals in positive Z direction and +@ref VertexFormat::Vector4 colors. Vertex colors correspond to the gradient +defined by the endpoints @p a and @p b, linearly interpolated from @p colorA to +@p colorB. @image html primitives-gradient3d.png width=256px @see @ref gradient3DHorizontal(), @ref gradient3DVertical(), @ref gradient2D(), @ref planeSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB); /** @brief 3D plane with a horizontal gradient @@ -101,7 +105,7 @@ Equivalent to calling @ref gradient3D() like this: @see @ref gradient3DVertical(), @ref gradient2DHorizontal(), @ref planeSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight); /** @brief 3D plane with a vertical gradient @@ -114,7 +118,7 @@ Equivalent to calling @ref gradient3D() like this: @see @ref gradient3DHorizontal(), @ref gradient2DVertical(), @ref planeSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D gradient3DVertical(const Color4& colorBottom, const Color4& colorTop); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient3DVertical(const Color4& colorBottom, const Color4& colorTop); }} diff --git a/src/Magnum/Primitives/Grid.cpp b/src/Magnum/Primitives/Grid.cpp index 498d42d0dd..68f434710d 100644 --- a/src/Magnum/Primitives/Grid.cpp +++ b/src/Magnum/Primitives/Grid.cpp @@ -27,82 +27,146 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) { +Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) { const Vector2i vertexCount = subdivisions + Vector2i{2}; const Vector2i faceCount = subdivisions + Vector2i{1}; - std::vector positions; - positions.reserve(vertexCount.product()); - for(Int y = 0; y != vertexCount.y(); ++y) - for(Int x = 0; x != vertexCount.x(); ++x) - positions.emplace_back((Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f); - - std::vector indices; - indices.reserve(faceCount.product()*6); - for(Int y = 0; y != faceCount.y(); ++y) { - for(Int x = 0; x != faceCount.x(); ++x) { - /* 2--1 5 - | / /| - |/ / | - 0 3--4 */ - indices.insert(indices.end(), { - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt((y + 1)*vertexCount.x() + x + 1), - UnsignedInt((y + 1)*vertexCount.x() + x + 0), - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt(y*vertexCount.x() + x + 1), - UnsignedInt((y + 1)*vertexCount.x() + x + 1)}); + /* Indices */ + Containers::Array indexData{std::size_t(faceCount.product()*6)*sizeof(UnsignedInt)}; + auto indices = Containers::arrayCast(indexData); + { + std::size_t i = 0; + for(Int y = 0; y != faceCount.y(); ++y) { + for(Int x = 0; x != faceCount.x(); ++x) { + /* 2--1 5 + | / /| + |/ / | + 0 3--4 */ + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x + 1); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x + 0); + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt(y*vertexCount.x() + x + 1); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x + 1); + } } } - std::vector> normals; - if(flags & GridFlag::GenerateNormals) - normals.emplace_back(positions.size(), Vector3::zAxis(1.0f)); + /* Allocate interleaved array for all vertex data */ + std::size_t stride = sizeof(Vector3); + std::size_t attributeCount = 1; + if(flags & GridFlag::GenerateNormals) { + ++attributeCount; + stride += sizeof(Vector3); + } + if(flags & GridFlag::GenerateTextureCoords) { + ++attributeCount; + stride += sizeof(Vector2); + } + Containers::Array vertexData{stride*vertexCount.product()}; + Containers::Array attributes{attributeCount}; + std::size_t attributeIndex = 0; + std::size_t attributeOffset = 0; + + /* Fill positions */ + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.begin()), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}; + attributeOffset += sizeof(Vector3); + { + std::size_t i = 0; + for(Int y = 0; y != vertexCount.y(); ++y) + for(Int x = 0; x != vertexCount.x(); ++x) + positions[i++] = {(Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f}; + } + + /* Fill normals, if any. It's always the second attribute, right after + positions. */ + if(flags & GridFlag::GenerateNormals) { + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.begin() + attributeOffset), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}; + attributeOffset += sizeof(Vector3); + for(auto&& i: normals) i = Vector3::zAxis(1.0f); + } - std::vector> textureCoordinates; if(flags & GridFlag::GenerateTextureCoords) { - textureCoordinates.emplace_back(); - textureCoordinates[0].reserve(positions.size()); + Containers::StridedArrayView1D textureCoords{vertexData, + reinterpret_cast(vertexData.begin() + attributeOffset), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, textureCoords}; + attributeOffset += sizeof(Vector2); for(std::size_t i = 0; i != positions.size(); ++i) - textureCoordinates[0].emplace_back(positions[i].xy()*0.5f + Vector2{0.5f}); + textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; } - return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, std::move(normals), std::move(textureCoordinates), {}, nullptr}; + /* Not using a compile-time attribute array because there's way too many + combinations */ + return Trade::MeshData{MeshPrimitive::Triangles, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), std::move(attributes)}; } -Trade::MeshData3D grid3DWireframe(const Vector2i& subdivisions) { +namespace { + +constexpr Trade::MeshAttributeData AttributeData3DWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; + +} + +Trade::MeshData grid3DWireframe(const Vector2i& subdivisions) { const Vector2i vertexCount = subdivisions + Vector2i{2}; const Vector2i faceCount = subdivisions + Vector2i{1}; - std::vector positions; - positions.reserve(vertexCount.product()); - for(Int y = 0; y != vertexCount.y(); ++y) - for(Int x = 0; x != vertexCount.x(); ++x) - positions.emplace_back((Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f); - - std::vector indices; - indices.reserve(vertexCount.y()*(vertexCount.x() - 1)*2 + - vertexCount.x()*(vertexCount.y() - 1)*2); - for(Int y = 0; y != vertexCount.y(); ++y) { - for(Int x = 0; x != vertexCount.x(); ++x) { - /* 3 7 - | | ... - 2 6 - 0--1 4--5 ... */ - if(x != vertexCount.x() - 1) indices.insert(indices.end(), { - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt(y*vertexCount.x() + x + 1)}); - if(y != vertexCount.y() - 1) indices.insert(indices.end(), { - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt((y + 1)*vertexCount.x() + x)}); + Containers::Array indexData{sizeof(UnsignedInt)* + (vertexCount.y()*(vertexCount.x() - 1)*2 + + vertexCount.x()*(vertexCount.y() - 1)*2)}; + auto indices = Containers::arrayCast(indexData); + { + std::size_t i = 0; + for(Int y = 0; y != vertexCount.y(); ++y) { + for(Int x = 0; x != vertexCount.x(); ++x) { + /* 3 7 + | | ... + 2 6 + 0--1 4--5 ... */ + if(x != vertexCount.x() - 1) { + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt(y*vertexCount.x() + x + 1); + } + if(y != vertexCount.y() - 1) { + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x); + } + } } } - return Trade::MeshData3D{MeshPrimitive::Lines, std::move(indices), {std::move(positions)}, {}, {}, {}, nullptr}; + Containers::Array vertexData{sizeof(Vector3)*vertexCount.product()}; + auto positions = Containers::arrayCast(vertexData); + { + std::size_t i = 0; + for(Int y = 0; y != vertexCount.y(); ++y) + for(Int x = 0; x != vertexCount.x(); ++x) + positions[i++] = {(Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f}; + } + + return Trade::MeshData{MeshPrimitive::Lines, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(AttributeData3DWireframe), + UnsignedInt(vertexCount.product())}; } }} diff --git a/src/Magnum/Primitives/Grid.h b/src/Magnum/Primitives/Grid.h index 09f7b8779b..60f524e07e 100644 --- a/src/Magnum/Primitives/Grid.h +++ b/src/Magnum/Primitives/Grid.h @@ -66,8 +66,11 @@ CORRADE_ENUMSET_OPERATORS(GridFlags) /** @brief Solid 3D grid -2x2 grid in the XY plane with normals in positive Z direction. Indexed -@ref MeshPrimitive::Triangles with optional normals and texture coordinates. +2x2 grid in the XY plane with normals in positive Z direction. +@ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, +interleaved @ref VertexFormat::Vector3 positions, optional +@ref VertexFormat::Vector3 normals and @ref VertexFormat::Vector2 texture +coordinates. @image html primitives-grid3dsolid.png width=256px @@ -78,12 +81,14 @@ cells horizontally and 4 vertically. In particular, this is different from the `subdivisions` parameter in @ref icosphereSolid(). @see @ref grid3DWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D grid3DSolid(const Vector2i& subdivisions, GridFlags flags = GridFlag::GenerateNormals); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData grid3DSolid(const Vector2i& subdivisions, GridFlags flags = GridFlag::GenerateNormals); /** @brief Wireframe 3D grid -2x2 grid in the XY plane. Indexed @ref MeshPrimitive::Lines. +2x2 grid in the XY plane. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-grid3dwireframe.png width=256px @@ -91,10 +96,12 @@ The @p subdivisions parameter describes how many times the plane gets cut in each direction. Specifying @cpp {0, 0} @ce will make the result an (indexed) equivalent to @ref planeWireframe(); @cpp {5, 3} @ce will make the grid have 6 cells horizontally and 4 vertically. In particular, this is different from the -`subdivisions` parameter in @ref icosphereSolid(). +`subdivisions` parameter in @ref icosphereSolid(). Also please note the grid +has vertices in each intersection to be suitable for deformation along the Z +axis --- not just long lines crossing each other. @see @ref grid3DSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D grid3DWireframe(const Vector2i& subdivisions); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData grid3DWireframe(const Vector2i& subdivisions); }} diff --git a/src/Magnum/Primitives/Icosphere.cpp b/src/Magnum/Primitives/Icosphere.cpp index f43ab09d41..2ee07eb888 100644 --- a/src/Magnum/Primitives/Icosphere.cpp +++ b/src/Magnum/Primitives/Icosphere.cpp @@ -25,63 +25,103 @@ #include "Icosphere.h" +#include + #include "Magnum/Mesh.h" -#include "Magnum/Math/Color.h" -#include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Subdivide.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D icosphereSolid(const UnsignedInt subdivisions) { - std::vector indices{ - 1, 2, 6, - 1, 7, 2, - 3, 4, 5, - 4, 3, 8, - 6, 5, 11, - 5, 6, 10, - 9, 10, 2, - 10, 9, 3, - 7, 8, 9, - 8, 7, 0, - 11, 0, 1, - 0, 11, 4, - 6, 2, 10, - 1, 6, 11, - 3, 5, 10, - 5, 4, 11, - 2, 7, 9, - 7, 1, 0, - 3, 9, 8, - 4, 8, 0 - }; +namespace { + +constexpr UnsignedInt Indices[]{ + 1, 2, 6, + 1, 7, 2, + 3, 4, 5, + 4, 3, 8, + 6, 5, 11, + 5, 6, 10, + 9, 10, 2, + 10, 9, 3, + 7, 8, 9, + 8, 7, 0, + 11, 0, 1, + 0, 11, 4, + 6, 2, 10, + 1, 6, 11, + 3, 5, 10, + 5, 4, 11, + 2, 7, 9, + 7, 1, 0, + 3, 9, 8, + 4, 8, 0 +}; + +constexpr Vector3 Positions[]{ + {0.0f, -0.525731f, 0.850651f}, + {0.850651f, 0.0f, 0.525731f}, + {0.850651f, 0.0f, -0.525731f}, + {-0.850651f, 0.0f, -0.525731f}, + {-0.850651f, 0.0f, 0.525731f}, + {-0.525731f, 0.850651f, 0.0f}, + {0.525731f, 0.850651f, 0.0f}, + {0.525731f, -0.850651f, 0.0f}, + {-0.525731f, -0.850651f, 0.0f}, + {0.0f, -0.525731f, -0.850651f}, + {0.0f, 0.525731f, -0.850651f}, + {0.0f, 0.525731f, 0.850651f} +}; + +} + +Trade::MeshData icosphereSolid(const UnsignedInt subdivisions) { + const std::size_t indexCount = Containers::arraySize(Indices)*(1 << subdivisions*2); + const std::size_t vertexCount = Containers::arraySize(Positions) + ((indexCount - Containers::arraySize(Indices))/3); + + Containers::Array indexData{indexCount*sizeof(UnsignedInt)}; + auto indices = Containers::arrayCast(indexData); + std::memcpy(indices.begin(), Indices, sizeof(Indices)); - std::vector positions{ - {0.0f, -0.525731f, 0.850651f}, - {0.850651f, 0.0f, 0.525731f}, - {0.850651f, 0.0f, -0.525731f}, - {-0.850651f, 0.0f, -0.525731f}, - {-0.850651f, 0.0f, 0.525731f}, - {-0.525731f, 0.850651f, 0.0f}, - {0.525731f, 0.850651f, 0.0f}, - {0.525731f, -0.850651f, 0.0f}, - {-0.525731f, -0.850651f, 0.0f}, - {0.0f, -0.525731f, -0.850651f}, - {0.0f, 0.525731f, -0.850651f}, - {0.0f, 0.525731f, 0.850651f} + struct Vertex { + Vector3 position; + Vector3 normal; }; + Containers::Array vertexData; + arrayResize(vertexData, Containers::NoInit, sizeof(Vertex)*vertexCount); + + /* Build up the subdivided positions */ + { + auto vertices = Containers::arrayCast(vertexData); + Containers::StridedArrayView1D positions{vertices, &vertices[0].position, vertices.size(), sizeof(Vertex)}; + for(std::size_t i = 0; i != Containers::arraySize(Positions); ++i) + positions[i] = Positions[i]; + + for(std::size_t i = 0; i != subdivisions; ++i) { + const std::size_t iterationIndexCount = Containers::arraySize(Indices)*(1 << (i + 1)*2); + const std::size_t iterationVertexCount = Containers::arraySize(Positions) + ((iterationIndexCount - Containers::arraySize(Indices))/3); + MeshTools::subdivideInPlace(indices.prefix(iterationIndexCount), positions.prefix(iterationVertexCount), [](const Vector3& a, const Vector3& b) { + return (a+b).normalized(); + }); + } - for(std::size_t i = 0; i != subdivisions; ++i) - MeshTools::subdivide(indices, positions, [](const Vector3& a, const Vector3& b) { - return (a+b).normalized(); - }); + /** @todo i need arrayShrinkAndGiveUpMemoryIfItDoesntCauseRealloc() */ + arrayResize(vertexData, MeshTools::removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(positions))*sizeof(Vertex)); + } - indices = MeshTools::duplicate(indices, MeshTools::removeDuplicates(positions)); + /* Build up the views again with correct size, fill the normals */ + auto vertices = Containers::arrayCast(vertexData); + Containers::StridedArrayView1D positions{vertices, &vertices[0].position, vertices.size(), sizeof(Vertex)}; + Containers::StridedArrayView1D normals{vertices, &vertices[0].normal, vertices.size(), sizeof(Vertex)}; + for(std::size_t i = 0; i != positions.size(); ++i) + normals[i] = positions[i]; - std::vector normals(positions); - return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, {}, {}, nullptr}; + return Trade::MeshData{MeshPrimitive::Triangles, std::move(indexData), + Trade::MeshIndexData{indices}, std::move(vertexData), + {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}}}; } }} diff --git a/src/Magnum/Primitives/Icosphere.h b/src/Magnum/Primitives/Icosphere.h index b1bceb884b..9a7981cf16 100644 --- a/src/Magnum/Primitives/Icosphere.h +++ b/src/Magnum/Primitives/Icosphere.h @@ -38,8 +38,9 @@ namespace Magnum { namespace Primitives { @brief Solid 3D icosphere @param subdivisions Number of subdivisions -Sphere with radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Triangles with -normals. +Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions and @ref VertexFormat::Vector3 normals. @image html primitives-icospheresolid.png width=256px @@ -51,7 +52,7 @@ result in 320 faces and so on. In particular, this is different from the `subdivisions` parameter in @ref grid3DSolid() or @ref grid3DWireframe(). @see @ref uvSphereSolid(), @ref uvSphereWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D icosphereSolid(UnsignedInt subdivisions); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData icosphereSolid(UnsignedInt subdivisions); }} diff --git a/src/Magnum/Primitives/Implementation/Spheroid.cpp b/src/Magnum/Primitives/Implementation/Spheroid.cpp index 9af9a762da..377ce59ca8 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.cpp +++ b/src/Magnum/Primitives/Implementation/Spheroid.cpp @@ -25,25 +25,59 @@ #include "Spheroid.h" +#include + #include "Magnum/Math/Functions.h" #include "Magnum/Math/Color.h" #include "Magnum/Mesh.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Implementation { -Spheroid::Spheroid(UnsignedInt segments, TextureCoords textureCoords): segments(segments), textureCoords(textureCoords) {} +Spheroid::Spheroid(UnsignedInt segments, TextureCoords textureCoords): _segments(segments), _textureCoords(textureCoords) {} -void Spheroid::capVertex(Float y, Float normalY, Float textureCoordsV) { - positions.emplace_back(0.0f, y, 0.0f); - normals.emplace_back(0.0f, normalY, 0.0f); +namespace { + +struct Vertex { + Vector3 position; + Vector3 normal; +}; + +struct VertexTextureCoords { + Vector3 position; + Vector3 normal; + Vector2 textureCoords; +}; + +} - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(0.5, textureCoordsV); +/** @todo gah this is fugly, any idea how to do this less awful? expose + arrayGrow? also, with current growth strategy this might realloc too much + at the beginning since the growth is optimized for adding a single + element */ +void Spheroid::append(const Vector3& position, const Vector3& normal, const Vector2& textureCoords) { + if(_textureCoords == TextureCoords::Generate) { + const VertexTextureCoords v[]{{position, normal, textureCoords}}; + arrayAppend(_vertexData, Containers::arrayCast(Containers::arrayView(v))); + } else { + const Vertex v[]{{position, normal}}; + arrayAppend(_vertexData, Containers::arrayCast(Containers::arrayView(v))); + } +} + +void Spheroid::setLastVertexTextureCoords(const Vector2& textureCoords) { + /* Assuming append() was called before */ + Containers::arrayCast(_vertexData).back().textureCoords = textureCoords; +} + +void Spheroid::capVertex(Float y, Float normalY, Float textureCoordsV) { + append({0.0f, y, 0.0f}, {0.0f, normalY, 0.0f}); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({0.5f, textureCoordsV}); } void Spheroid::hemisphereVertexRings(UnsignedInt count, Float centerY, Rad startRingAngle, Rad ringAngleIncrement, Float startTextureCoordsV, Float textureCoordsVIncrement) { - const Rad segmentAngleIncrement(Constants::tau()/segments); + const Rad segmentAngleIncrement(Constants::tau()/_segments); for(UnsignedInt i = 0; i != count; ++i) { const Rad ringAngle = startRingAngle + Float(i)*ringAngleIncrement; const std::pair ringSinCos = Math::sincos(ringAngle); @@ -51,21 +85,23 @@ void Spheroid::hemisphereVertexRings(UnsignedInt count, Float centerY, Rad start const Float z = ringSinCos.second; const Float y = ringSinCos.first; - for(UnsignedInt j = 0; j != segments; ++j) { + for(UnsignedInt j = 0; j != _segments; ++j) { const Rad segmentAngle = Float(j)*segmentAngleIncrement; const std::pair segmentSinCos = Math::sincos(segmentAngle); - positions.emplace_back(x*segmentSinCos.first, centerY+y, z*segmentSinCos.second); - normals.emplace_back(x*segmentSinCos.first, y, z*segmentSinCos.second); + append({x*segmentSinCos.first, centerY+y, z*segmentSinCos.second}, + {x*segmentSinCos.first, y, z*segmentSinCos.second}); - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(j*1.0f/segments, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({j*1.0f/_segments, startTextureCoordsV + i*textureCoordsVIncrement}); } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(textureCoords == TextureCoords::Generate) { - positions.push_back(positions[positions.size()-segments]); - normals.push_back(normals[normals.size()-segments]); - textureCoords2D.emplace_back(1.0f, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) { + /* This view will become dangling right after append() */ + auto typedVertices = Containers::arrayCast(_vertexData); + append(typedVertices[typedVertices.size()-_segments].position, + typedVertices[typedVertices.size()-_segments].normal, + {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}); } } } @@ -74,23 +110,25 @@ void Spheroid::cylinderVertexRings(const UnsignedInt count, const Float startY, const Vector2 baseNormal = -increment.perpendicular().normalized(); Vector2 base = {1.0f, startY}; - const Rad segmentAngleIncrement(Constants::tau()/segments); + const Rad segmentAngleIncrement(Constants::tau()/_segments); for(UnsignedInt i = 0; i != count; ++i) { - for(UnsignedInt j = 0; j != segments; ++j) { + for(UnsignedInt j = 0; j != _segments; ++j) { const Rad segmentAngle = Float(j)*segmentAngleIncrement; const std::pair segmentSinCos = Math::sincos(segmentAngle); - positions.emplace_back(base.x()*segmentSinCos.first, base.y(), base.x()*segmentSinCos.second); - normals.emplace_back(baseNormal.x()*segmentSinCos.first, baseNormal.y(), baseNormal.x()*segmentSinCos.second); + append({base.x()*segmentSinCos.first, base.y(), base.x()*segmentSinCos.second}, + {baseNormal.x()*segmentSinCos.first, baseNormal.y(), baseNormal.x()*segmentSinCos.second}); - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(j*1.0f/segments, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({j*1.0f/_segments, startTextureCoordsV + i*textureCoordsVIncrement}); } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(textureCoords == TextureCoords::Generate) { - positions.push_back(positions[positions.size()-segments]); - normals.push_back(normals[normals.size()-segments]); - textureCoords2D.emplace_back(1.0f, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) { + /* This view will become dangling right after append() */ + auto typedVertices = Containers::arrayCast(_vertexData); + append(typedVertices[typedVertices.size()-_segments].position, + typedVertices[typedVertices.size()-_segments].normal, + {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}); } base += increment; @@ -98,80 +136,123 @@ void Spheroid::cylinderVertexRings(const UnsignedInt count, const Float startY, } void Spheroid::bottomFaceRing() { - for(UnsignedInt j = 0; j != segments; ++j) { - /* Bottom vertex */ - indices.push_back(0); - - /* Top right vertex */ - indices.push_back((j != segments-1 || textureCoords == TextureCoords::Generate) ? - j+2 : 1); - - /* Top left vertex */ - indices.push_back(j+1); + for(UnsignedInt j = 0; j != _segments; ++j) { + arrayAppend(_indexData, { + /* Bottom vertex */ + 0u, + + /* Top right vertex */ + (j != _segments-1 || _textureCoords == TextureCoords::Generate) ? + j+2 : 1, + + /* Top left vertex */ + j+1 + }); } } void Spheroid::faceRings(UnsignedInt count, UnsignedInt offset) { - const UnsignedInt vertexSegments = segments + (textureCoords == TextureCoords::Generate ? 1 : 0); + const UnsignedInt vertexSegments = _segments + (_textureCoords == TextureCoords::Generate ? 1 : 0); for(UnsignedInt i = 0; i != count; ++i) { - for(UnsignedInt j = 0; j != segments; ++j) { + for(UnsignedInt j = 0; j != _segments; ++j) { const UnsignedInt bottomLeft = i*vertexSegments+j+offset; - const UnsignedInt bottomRight = ((j != segments-1 || textureCoords == TextureCoords::Generate) ? - i*vertexSegments+j+1+offset : i*segments+offset); + const UnsignedInt bottomRight = ((j != _segments-1 || _textureCoords == TextureCoords::Generate) ? + i*vertexSegments+j+1+offset : i*_segments+offset); const UnsignedInt topLeft = bottomLeft+vertexSegments; const UnsignedInt topRight = bottomRight+vertexSegments; - indices.push_back(bottomLeft); - indices.push_back(bottomRight); - indices.push_back(topRight); - indices.push_back(bottomLeft); - indices.push_back(topRight); - indices.push_back(topLeft); + arrayAppend(_indexData, { + bottomLeft, + bottomRight, + topRight, + bottomLeft, + topRight, + topLeft + }); } } } void Spheroid::topFaceRing() { - const UnsignedInt vertexSegments = segments + (textureCoords == TextureCoords::Generate ? 1 : 0); - - for(UnsignedInt j = 0; j != segments; ++j) { - /* Bottom left vertex */ - indices.push_back(normals.size()-vertexSegments+j-1); - - /* Bottom right vertex */ - indices.push_back((j != segments-1 || textureCoords == TextureCoords::Generate) ? - normals.size()-vertexSegments+j : normals.size()-segments-1); - - /* Top vertex */ - indices.push_back(normals.size()-1); + const UnsignedInt vertexSegments = _segments + (_textureCoords == TextureCoords::Generate ? 1 : 0); + + UnsignedInt vertexCount; + if(_textureCoords == TextureCoords::Generate) + vertexCount = _vertexData.size()/sizeof(VertexTextureCoords); + else + vertexCount = _vertexData.size()/sizeof(Vertex); + + for(UnsignedInt j = 0; j != _segments; ++j) { + arrayAppend(_indexData, { + /* Bottom left vertex */ + vertexCount - vertexSegments + j - 1, + + /* Bottom right vertex */ + (j != _segments-1 || _textureCoords == TextureCoords::Generate) ? + vertexCount - vertexSegments + j : vertexCount - _segments - 1, + + /* Top vertex */ + vertexCount - 1 + }); } } void Spheroid::capVertexRing(Float y, Float textureCoordsV, const Vector3& normal) { - const Rad segmentAngleIncrement(Constants::tau()/segments); + const Rad segmentAngleIncrement(Constants::tau()/_segments); - for(UnsignedInt i = 0; i != segments; ++i) { + for(UnsignedInt i = 0; i != _segments; ++i) { const Rad segmentAngle = Float(i)*segmentAngleIncrement; const std::pair segmentSinCos = Math::sincos(segmentAngle); - positions.emplace_back(segmentSinCos.first, y, segmentSinCos.second); - normals.push_back(normal); + append({segmentSinCos.first, y, segmentSinCos.second}, normal); - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(i*1.0f/segments, textureCoordsV); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({i*1.0f/_segments, textureCoordsV}); } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(textureCoords == TextureCoords::Generate) { - positions.push_back(positions[positions.size()-segments]); - normals.push_back(normal); - textureCoords2D.emplace_back(1.0f, textureCoordsV); + if(_textureCoords == TextureCoords::Generate) { + /* This view will become dangling right after append() */ + auto typedVertices = Containers::arrayCast(_vertexData); + append(typedVertices[typedVertices.size()-_segments].position, + normal, + {1.0f, textureCoordsV}); } } -Trade::MeshData3D Spheroid::finalize() { - return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, - textureCoords == TextureCoords::Generate ? std::vector>{std::move(textureCoords2D)} : std::vector>(), {}, nullptr}; +namespace { + +constexpr Trade::MeshAttributeData AttributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + offsetof(Vertex, position), 0, sizeof(Vertex)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + offsetof(Vertex, normal), 0, sizeof(Vertex)} +}; + +constexpr Trade::MeshAttributeData AttributeDataTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + offsetof(VertexTextureCoords, position), 0, sizeof(VertexTextureCoords)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + offsetof(VertexTextureCoords, normal), 0, sizeof(VertexTextureCoords)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + offsetof(VertexTextureCoords, textureCoords), 0, sizeof(VertexTextureCoords)} +}; + +} + +Trade::MeshData Spheroid::finalize() { + Trade::MeshIndexData indices{_indexData}; + + Containers::Array attributes; + if(_textureCoords == TextureCoords::Generate) + attributes = Trade::meshAttributeDataNonOwningArray(AttributeDataTextureCoords); + else + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData); + const UnsignedInt vertexCount = _vertexData.size()/attributes[0].stride(); + + return Trade::MeshData{MeshPrimitive::Triangles, + Containers::arrayAllocatorCast(std::move(_indexData)), indices, + std::move(_vertexData), std::move(attributes), vertexCount}; } }}} diff --git a/src/Magnum/Primitives/Implementation/Spheroid.h b/src/Magnum/Primitives/Implementation/Spheroid.h index 4162f716ad..62f03afe94 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.h +++ b/src/Magnum/Primitives/Implementation/Spheroid.h @@ -25,9 +25,11 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include +#include #include "Magnum/Magnum.h" +#include "Magnum/Math/Vector2.h" #include "Magnum/Trade/Trade.h" namespace Magnum { namespace Primitives { namespace Implementation { @@ -49,16 +51,17 @@ class Spheroid { void topFaceRing(); void capVertexRing(Float y, Float textureCoordsV, const Vector3& normal); - Trade::MeshData3D finalize(); + Trade::MeshData finalize(); private: - UnsignedInt segments; - TextureCoords textureCoords; + UnsignedInt _segments; + TextureCoords _textureCoords; - std::vector indices; - std::vector positions; - std::vector normals; - std::vector textureCoords2D; + Containers::Array _indexData; + Containers::Array _vertexData; + + void append(const Vector3& position, const Vector3& normal, const Vector2& textureCoords = {}); + void setLastVertexTextureCoords(const Vector2& textureCoords); }; }}} diff --git a/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp b/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp index 64dccafcae..29212b416f 100644 --- a/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp +++ b/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp @@ -25,24 +25,26 @@ #include "WireframeSpheroid.h" +#include + #include "Magnum/Math/Functions.h" #include "Magnum/Math/Color.h" #include "Magnum/Mesh.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Implementation { WireframeSpheroid::WireframeSpheroid(const UnsignedInt segments): _segments(segments) {} void WireframeSpheroid::bottomHemisphere(const Float endY, const UnsignedInt rings) { - CORRADE_INTERNAL_ASSERT(_positions.empty()); + CORRADE_INTERNAL_ASSERT(_vertexData.empty()); /* Initial vertex */ - _positions.push_back(Vector3::yAxis(endY - 1.0f)); + arrayAppend(_vertexData, Vector3::yAxis(endY - 1.0f)); /* Connect initial vertex to first ring */ for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {0, i+1}); + arrayAppend(_indexData, {0u, i+1}); /* Hemisphere vertices and indices */ const Rad ringAngleIncrement(Constants::piHalf()/rings); @@ -50,21 +52,24 @@ void WireframeSpheroid::bottomHemisphere(const Float endY, const UnsignedInt rin const Rad angle = Float(j+1)*ringAngleIncrement; const std::pair sincos = Math::sincos(angle); - _positions.emplace_back(0.0f, endY - sincos.second, sincos.first); - _positions.emplace_back(sincos.first, endY - sincos.second, 0.0f); - _positions.emplace_back(0.0f, endY - sincos.second, -sincos.first); - _positions.emplace_back(-sincos.first, endY - sincos.second, 0.0f); + arrayAppend(_vertexData, { + {0.0f, endY - sincos.second, sincos.first}, + {sincos.first, endY - sincos.second, 0.0f}, + {0.0f, endY - sincos.second, -sincos.first}, + {-sincos.first, endY - sincos.second, 0.0f} + }); /* Connect vertices to next ring */ - for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4+i, UnsignedInt(_positions.size())+i}); + for(UnsignedInt i = 0; i != 4; ++i) { + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4 + i, UnsignedInt(_vertexData.size()) + i}); + } } } void WireframeSpheroid::topHemisphere(const Float startY, const UnsignedInt rings) { /* Connect previous ring to following vertices (if any) */ if(rings > 1) for(UnsignedInt i = 0; i != 4; ++i) { - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4*_segments+i, UnsignedInt(_positions.size())+i}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i, UnsignedInt(_vertexData.size()) + i}); } /* Hemisphere vertices and indices */ @@ -74,23 +79,26 @@ void WireframeSpheroid::topHemisphere(const Float startY, const UnsignedInt ring const std::pair sincos = Math::sincos(angle); /* Connect previous hemisphere ring to current vertices */ - if(j != 0) for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4+i, UnsignedInt(_positions.size())+i}); + if(j != 0) for(UnsignedInt i = 0; i != 4; ++i) { + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4 + i, UnsignedInt(_vertexData.size()) + i}); + } - _positions.emplace_back(0.0f, startY + sincos.first, sincos.second); - _positions.emplace_back(sincos.second, startY + sincos.first, 0.0f); - _positions.emplace_back(0.0f, startY + sincos.first, -sincos.second); - _positions.emplace_back(-sincos.second, startY + sincos.first, 0.0f); + arrayAppend(_vertexData, { + {0.0f, startY + sincos.first, sincos.second}, + {sincos.second, startY + sincos.first, 0.0f}, + {0.0f, startY + sincos.first, -sincos.second}, + {-sincos.second, startY + sincos.first, 0.0f} + }); } /* Final vertex */ - _positions.push_back(Vector3::yAxis(startY + 1.0f)); + arrayAppend(_vertexData, Vector3::yAxis(startY + 1.0f)); /* Connect last ring to final vertex */ if(rings > 1) for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size()) -5 + i, UnsignedInt(_positions.size()) - 1}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 5 + i, UnsignedInt(_vertexData.size()) - 1}); else for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size()) - 4*_segments + i- 1 , UnsignedInt(_positions.size()) - 1}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i - 1, UnsignedInt(_vertexData.size()) - 1}); } void WireframeSpheroid::ring(const Float y) { @@ -100,24 +108,39 @@ void WireframeSpheroid::ring(const Float y) { for(UnsignedInt i = 0; i != 4; ++i) { const Rad segmentAngle = Rad(Float(i)*Constants::piHalf()) + Float(j)*segmentAngleIncrement; const std::pair sincos = Math::sincos(segmentAngle); - if(j != 0) _indices.insert(_indices.end(), {UnsignedInt(_positions.size()-4), UnsignedInt(_positions.size())}); - _positions.emplace_back(sincos.first, y, sincos.second); + if(j != 0) arrayAppend(_indexData, {UnsignedInt(_vertexData.size() - 4), UnsignedInt(_vertexData.size())}); + arrayAppend(_vertexData, {sincos.first, y, sincos.second}); } } /* Close the ring */ for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4+i, UnsignedInt(_positions.size())-4*_segments+(i+1)%4}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4 + i, UnsignedInt(_vertexData.size()) - 4*_segments + (i + 1)%4}); } void WireframeSpheroid::cylinder() { /* Connect four vertex pairs of previous and next ring */ for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4*_segments+i, UnsignedInt(_positions.size())+i}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i, UnsignedInt(_vertexData.size()) + i}); +} + +namespace { + +constexpr Trade::MeshAttributeData AttributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; + } -Trade::MeshData3D WireframeSpheroid::finalize() { - return Trade::MeshData3D{MeshPrimitive::Lines, std::move(_indices), {std::move(_positions)}, {}, {}, {}, nullptr}; +Trade::MeshData WireframeSpheroid::finalize() { + Trade::MeshIndexData indices{_indexData}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, Containers::arrayView(_vertexData)}; + const UnsignedInt vertexCount = _vertexData.size(); + return Trade::MeshData{MeshPrimitive::Lines, + Containers::arrayAllocatorCast(std::move(_indexData)), indices, + Containers::arrayAllocatorCast(std::move(_vertexData)), + Trade::meshAttributeDataNonOwningArray(AttributeData), vertexCount}; } }}} diff --git a/src/Magnum/Primitives/Implementation/WireframeSpheroid.h b/src/Magnum/Primitives/Implementation/WireframeSpheroid.h index 24bd3d2395..60042bcc7d 100644 --- a/src/Magnum/Primitives/Implementation/WireframeSpheroid.h +++ b/src/Magnum/Primitives/Implementation/WireframeSpheroid.h @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include #include "Magnum/Magnum.h" #include "Magnum/Trade/Trade.h" @@ -41,13 +41,13 @@ class WireframeSpheroid { void ring(Float y); void cylinder(); - Trade::MeshData3D finalize(); + Trade::MeshData finalize(); private: UnsignedInt _segments; - std::vector _indices; - std::vector _positions; + Containers::Array _indexData; + Containers::Array _vertexData; }; }}} diff --git a/src/Magnum/Primitives/Line.cpp b/src/Magnum/Primitives/Line.cpp index 2f78cba966..c688a9a848 100644 --- a/src/Magnum/Primitives/Line.cpp +++ b/src/Magnum/Primitives/Line.cpp @@ -27,24 +27,53 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D line2D(const Vector2& a, const Vector2& b) { - return Trade::MeshData2D{MeshPrimitive::Lines, {}, {{a, b}}, {}, {}, nullptr}; +namespace { + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, 0, 2, sizeof(Vector2)}, +}; + +} + +Trade::MeshData line2D(const Vector2& a, const Vector2& b) { + Containers::Array vertexData{sizeof(Vector2)*2}; + auto positions = Containers::arrayCast(vertexData); + positions[0] = a; + positions[1] = b; + + return Trade::MeshData{MeshPrimitive::Lines, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } -Trade::MeshData3D line3D(const Vector3& a, const Vector3& b) { - return Trade::MeshData3D{MeshPrimitive::Lines, {}, {{a, b}}, {}, {}, {}, nullptr}; +namespace { + +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, 0, 2, sizeof(Vector3)}, +}; + +} + +Trade::MeshData line3D(const Vector3& a, const Vector3& b) { + Containers::Array vertexData{sizeof(Vector3)*2}; + auto positions = Containers::arrayCast(vertexData); + positions[0] = a; + positions[1] = b; + + return Trade::MeshData{MeshPrimitive::Lines, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } -Trade::MeshData2D line2D() { +Trade::MeshData line2D() { return line2D({0.0f, 0.0f}, {1.0f, 0.0f}); } -Trade::MeshData3D line3D() { +Trade::MeshData line3D() { return line3D({0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}); } diff --git a/src/Magnum/Primitives/Line.h b/src/Magnum/Primitives/Line.h index 7bc9863d69..8c48eccfbd 100644 --- a/src/Magnum/Primitives/Line.h +++ b/src/Magnum/Primitives/Line.h @@ -38,14 +38,15 @@ namespace Magnum { namespace Primitives { /** @brief 2D line -Non-indexed @ref MeshPrimitive::Lines going from @p a to @p b. +Non-indexed @ref MeshPrimitive::Lines with @ref VertexFormat::Vector2 positions +going from @p a to @p b. @image html primitives-line2d.png width=256px @see @ref line3D(), @ref line3D(const Vector3&, const Vector3&), @ref axis2D(), @ref crosshair2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D line2D(const Vector2& a, const Vector2& b); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line2D(const Vector2& a, const Vector2& b); /** @brief 2D line in an identity transformation @@ -54,19 +55,20 @@ Equivalent to calling @ref line2D(const Vector2&, const Vector2&) as @snippet MagnumPrimitives.cpp line2D-identity */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D line2D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line2D(); /** @brief 3D line -Non-indexed @ref MeshPrimitive::Lines going from @p a to @p b. +Non-indexed @ref MeshPrimitive::Lines with @ref VertexFormat::Vector3 positions +going from @p a to @p b. @image html primitives-line3d.png width=256px @see @ref line3D(), @ref line2D(const Vector2&, const Vector2&), @ref axis3D(), @ref crosshair3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D line3D(const Vector3& a, const Vector3& b); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line3D(const Vector3& a, const Vector3& b); /** @brief 3D line in an identity transformation @@ -76,7 +78,7 @@ Unit-size line in direction of positive X axis. Equivalent to calling @snippet MagnumPrimitives.cpp line3D-identity */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D line3D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line3D(); }} diff --git a/src/Magnum/Primitives/Plane.cpp b/src/Magnum/Primitives/Plane.cpp index 48797668a2..ae41ba9c65 100644 --- a/src/Magnum/Primitives/Plane.cpp +++ b/src/Magnum/Primitives/Plane.cpp @@ -26,40 +26,92 @@ #include "Plane.h" #include "Magnum/Mesh.h" -#include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Math/Vector3.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D planeSolid(const PlaneTextureCoords textureCoords) { - std::vector> coords; - if(textureCoords == PlaneTextureCoords::Generate) coords.push_back({ - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 0.0f}, - {0.0f, 1.0f} - }); - - return Trade::MeshData3D{MeshPrimitive::TriangleStrip, {}, {{ - {1.0f, -1.0f, 0.0f}, - {1.0f, 1.0f, 0.0f}, - {-1.0f, -1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f} - }}, {{ - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f} - }}, std::move(coords), {}, nullptr}; +namespace { + +constexpr struct VertexSolid { + Vector3 position; + Vector3 normal; +} VerticesSolid[] { + {{ 1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}} +}; +constexpr struct VertexSolidTextureCoords { + Vector3 position; + Vector3 normal; + Vector2 textureCoords; +} VerticesSolidTextureCoords[] { + {VerticesSolid[0].position, VerticesSolid[0].normal, {1.0f, 0.0f}}, + {VerticesSolid[1].position, VerticesSolid[1].normal, {1.0f, 1.0f}}, + {VerticesSolid[2].position, VerticesSolid[2].normal, {0.0f, 0.0f}}, + {VerticesSolid[3].position, VerticesSolid[3].normal, {0.0f, 1.0f}} +}; +constexpr Trade::MeshAttributeData AttributesSolid[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].normal, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))} +}; +constexpr Trade::MeshAttributeData AttributesSolidTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].position, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].normal, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].textureCoords, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, +}; + +} + +Trade::MeshData planeSolid(const PlaneTextureCoords textureCoords) { + if(textureCoords != PlaneTextureCoords::Generate) + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolid, + Trade::meshAttributeDataNonOwningArray(AttributesSolid)}; + + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolidTextureCoords, + Trade::meshAttributeDataNonOwningArray(AttributesSolidTextureCoords)}; +} + +namespace { + +/* Can't be just an array of Vector3 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexWireframe { + Vector3 position; +} VerticesWireframe[]{ + {{-1.0f, -1.0f, 0.0f}}, + {{ 1.0f, -1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}} +}; +constexpr Trade::MeshAttributeData AttributesWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesWireframe, &VerticesWireframe[0].position, + Containers::arraySize(VerticesWireframe), sizeof(VertexWireframe))} +}; + } -Trade::MeshData3D planeWireframe() { - return Trade::MeshData3D{MeshPrimitive::LineLoop, {}, {{ - {-1.0f, -1.0f, 0.0f}, - {1.0f, -1.0f, 0.0f}, - {1.0f, 1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f} - }}, {}, {}, {}, nullptr}; +Trade::MeshData planeWireframe() { + return Trade::MeshData{MeshPrimitive::LineLoop, + {}, VerticesWireframe, + Trade::meshAttributeDataNonOwningArray(AttributesWireframe)}; } }} diff --git a/src/Magnum/Primitives/Plane.h b/src/Magnum/Primitives/Plane.h index 0f2cab9cd1..2bb902b81b 100644 --- a/src/Magnum/Primitives/Plane.h +++ b/src/Magnum/Primitives/Plane.h @@ -50,24 +50,28 @@ enum class PlaneTextureCoords: UnsignedByte { @brief Solid 3D plane 2x2 plane. Non-indexed @ref MeshPrimitive::TriangleStrip on the XY plane with -normals in positive Z direction. +@ref VertexFormat::Vector3 positions and @ref VertexFormat::Vector3 normals in +positive Z direction. The returned instance references data stored in constant +memory. @image html primitives-planesolid.png width=256px @see @ref planeWireframe(), @ref squareSolid(), @ref gradient3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D planeSolid(PlaneTextureCoords textureCoords = PlaneTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeSolid(PlaneTextureCoords textureCoords = PlaneTextureCoords::DontGenerate); /** @brief Wireframe 3D plane -2x2 plane. Non-indexed @ref MeshPrimitive::LineLoop on the XY plane. +2x2 plane. Non-indexed @ref MeshPrimitive::LineLoop on the XY plane with +@ref VertexFormat::Vector3 positions. The returned instance references data +stored in constant memory. @image html primitives-planewireframe.png width=256px @see @ref planeSolid(), @ref squareWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D planeWireframe(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeWireframe(); }} diff --git a/src/Magnum/Primitives/Square.cpp b/src/Magnum/Primitives/Square.cpp index 93c833e958..f0d9901923 100644 --- a/src/Magnum/Primitives/Square.cpp +++ b/src/Magnum/Primitives/Square.cpp @@ -27,34 +27,84 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D squareSolid(const SquareTextureCoords textureCoords) { - std::vector> coords; - if(textureCoords == SquareTextureCoords::Generate) coords.push_back({ - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 0.0f}, - {0.0f, 1.0f} - }); - - return Trade::MeshData2D{MeshPrimitive::TriangleStrip, {}, {{ - {1.0f, -1.0f}, - {1.0f, 1.0f}, - {-1.0f, -1.0f}, - {-1.0f, 1.0f} - }}, std::move(coords), {}, nullptr}; +namespace { + +/* Can't be just an array of Vector2 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexSolid { + Vector2 position; +} VerticesSolid[] { + {{ 1.0f, -1.0f}}, + {{ 1.0f, 1.0f}}, + {{-1.0f, -1.0f}}, + {{-1.0f, 1.0f}} +}; +constexpr struct VertexSolidTextureCoords { + Vector2 position; + Vector2 textureCoords; +} VerticesSolidTextureCoords[] { + {{ 1.0f, -1.0f}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{-1.0f, -1.0f}, {0.0f, 0.0f}}, + {{-1.0f, 1.0f}, {0.0f, 1.0f}} +}; +constexpr Trade::MeshAttributeData AttributesSolid[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}, +}; +constexpr Trade::MeshAttributeData AttributesSolidTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].position, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].textureCoords, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))} +}; + +} + +Trade::MeshData squareSolid(const SquareTextureCoords textureCoords) { + if(textureCoords != SquareTextureCoords::Generate) + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolid, + Trade::meshAttributeDataNonOwningArray(AttributesSolid)}; + + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolidTextureCoords, + Trade::meshAttributeDataNonOwningArray(AttributesSolidTextureCoords)}; +} + +namespace { + +/* Can't be just an array of Vector2 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexWireframe { + Vector2 position; +} VerticesWireframe[]{ + {{-1.0f, -1.0f}}, + {{ 1.0f, -1.0f}}, + {{ 1.0f, 1.0f}}, + {{-1.0f, 1.0f}} +}; +constexpr Trade::MeshAttributeData AttributesWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesWireframe, &VerticesWireframe[0].position, + Containers::arraySize(VerticesWireframe), sizeof(VertexWireframe))} +}; + } -Trade::MeshData2D squareWireframe() { - return Trade::MeshData2D{MeshPrimitive::LineLoop, {}, {{ - {-1.0f, -1.0f}, - {1.0f, -1.0f}, - {1.0f, 1.0f}, - {-1.0f, 1.0f} - }}, {}, {}, nullptr}; +Trade::MeshData squareWireframe() { + return Trade::MeshData{MeshPrimitive::LineLoop, + {}, VerticesWireframe, + Trade::meshAttributeDataNonOwningArray(AttributesWireframe)}; } }} diff --git a/src/Magnum/Primitives/Square.h b/src/Magnum/Primitives/Square.h index 4e0940dc9b..5db993e926 100644 --- a/src/Magnum/Primitives/Square.h +++ b/src/Magnum/Primitives/Square.h @@ -49,24 +49,29 @@ enum class SquareTextureCoords: UnsignedByte { /** @brief Solid 2D square -2x2 square. Non-indexed @ref MeshPrimitive::TriangleStrip. +2x2 square. Non-indexed @ref MeshPrimitive::TriangleStrip with interleaved +@ref VertexFormat::Vector2 positions and optional @ref VertexFormat::Vector2 +texture coordinates. The returned instance references data stored in constant +memory. @image html primitives-squaresolid.png width=256px @see @ref squareWireframe(), @ref planeSolid(), @ref gradient2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D squareSolid(SquareTextureCoords textureCoords = SquareTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData squareSolid(SquareTextureCoords textureCoords = SquareTextureCoords::DontGenerate); /** @brief Wireframe 2D square -2x2 square. Non-indexed @ref MeshPrimitive::LineLoop. +2x2 square. Non-indexed @ref MeshPrimitive::LineLoop with +@ref VertexFormat::Vector2 positions. The returned instance references data +stored in constant memory. @image html primitives-squarewireframe.png width=256px @see @ref squareSolid(), @ref planeWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D squareWireframe(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData squareWireframe(); }} diff --git a/src/Magnum/Primitives/Test/AxisTest.cpp b/src/Magnum/Primitives/Test/AxisTest.cpp index f1f93ac364..56ac3f5a6e 100644 --- a/src/Magnum/Primitives/Test/AxisTest.cpp +++ b/src/Magnum/Primitives/Test/AxisTest.cpp @@ -28,8 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Axis.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -46,21 +45,33 @@ AxisTest::AxisTest() { } void AxisTest::twoDimensions() { - Trade::MeshData2D axis = Primitives::axis2D(); + Trade::MeshData axis = Primitives::axis2D(); CORRADE_COMPARE(axis.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(axis.indices().size(), 12); - CORRADE_COMPARE(axis.positions(0).size(), 8); - CORRADE_COMPARE(axis.colors(0).size(), 8); + CORRADE_VERIFY(axis.isIndexed()); + CORRADE_COMPARE(axis.indexCount(), 12); + CORRADE_COMPARE(axis.vertexCount(), 8); + CORRADE_COMPARE(axis.attributeCount(), 2); + CORRADE_COMPARE(axis.indices()[5], 3); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{0.9f, -0.1f})); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Color)[6], + (Color3{0.0f, 1.0f, 0.0f})); } void AxisTest::threeDimensions() { - Trade::MeshData3D axis = Primitives::axis3D(); + Trade::MeshData axis = Primitives::axis3D(); CORRADE_COMPARE(axis.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(axis.indices().size(), 18); - CORRADE_COMPARE(axis.positions(0).size(), 12); - CORRADE_COMPARE(axis.colors(0).size(), 12); + CORRADE_VERIFY(axis.isIndexed()); + CORRADE_COMPARE(axis.indexCount(), 18); + CORRADE_COMPARE(axis.vertexCount(), 12); + CORRADE_COMPARE(axis.attributeCount(), 2); + CORRADE_COMPARE(axis.indices()[12], 8); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Position)[6], + (Vector3{0.1f, 0.9f, 0.0f})); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Color)[4], + (Color3{0.0f, 1.0f, 0.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/CapsuleTest.cpp b/src/Magnum/Primitives/Test/CapsuleTest.cpp index 4ae068aaab..bcb3578859 100644 --- a/src/Magnum/Primitives/Test/CapsuleTest.cpp +++ b/src/Magnum/Primitives/Test/CapsuleTest.cpp @@ -27,8 +27,7 @@ #include #include "Magnum/Math/Vector3.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Primitives/Capsule.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -51,9 +50,13 @@ CapsuleTest::CapsuleTest() { } void CapsuleTest::wireframe2D() { - Trade::MeshData2D capsule = capsule2DWireframe(2, 4, 0.5f); + Trade::MeshData capsule = capsule2DWireframe(2, 4, 0.5f); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 1); + + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f}, {-0.707107f, -1.20711f}, @@ -80,7 +83,7 @@ void CapsuleTest::wireframe2D() { {0.0f, 1.5f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 1, 0, 2, 1, 3, 2, 4, @@ -95,9 +98,13 @@ void CapsuleTest::wireframe2D() { } void CapsuleTest::solid3DWithoutTextureCoords() { - Trade::MeshData3D capsule = capsule3DSolid(2, 4, 3, 0.5f); + Trade::MeshData capsule = capsule3DSolid(2, 4, 3, 0.5f); + + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 2); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, {0.0f, -1.20711f, 0.707107f}, @@ -131,7 +138,7 @@ void CapsuleTest::solid3DWithoutTextureCoords() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.normals(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.707107f, 0.707107f}, @@ -165,7 +172,7 @@ void CapsuleTest::solid3DWithoutTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 5, 1, 5, 4, 2, 3, 6, 2, 6, 5, 3, 1, 4, 3, 4, 6, 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, @@ -178,9 +185,13 @@ void CapsuleTest::solid3DWithoutTextureCoords() { } void CapsuleTest::solid3DWithTextureCoords() { - Trade::MeshData3D capsule = capsule3DSolid(2, 2, 3, 0.5f, CapsuleTextureCoords::Generate); + Trade::MeshData capsule = capsule3DSolid(2, 2, 3, 0.5f, CapsuleTextureCoords::Generate); + + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 3); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, {0.0f, -1.20711f, 0.707107f}, @@ -211,7 +222,7 @@ void CapsuleTest::solid3DWithTextureCoords() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, {0.0f, 0.166667f}, @@ -242,7 +253,7 @@ void CapsuleTest::solid3DWithTextureCoords() { {0.5f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 3, 4, 8, 3, 8, 7, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, 7, 8, 12, 7, 12, 11, @@ -253,9 +264,13 @@ void CapsuleTest::solid3DWithTextureCoords() { } void CapsuleTest::wireframe3D() { - Trade::MeshData3D capsule = capsule3DWireframe(2, 2, 8, 0.5f); + Trade::MeshData capsule = capsule3DWireframe(2, 2, 8, 0.5f); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 1); + + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, {0.0f, -1.20711f, 0.707107f}, @@ -298,9 +313,7 @@ void CapsuleTest::wireframe3D() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(capsule.normalArrayCount(), 0); - - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 1, 0, 2, 0, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 8, 5, 9, 6, 10, 7, 11, 8, 12, diff --git a/src/Magnum/Primitives/Test/CircleTest.cpp b/src/Magnum/Primitives/Test/CircleTest.cpp index 92dda4a6c5..df97ad2940 100644 --- a/src/Magnum/Primitives/Test/CircleTest.cpp +++ b/src/Magnum/Primitives/Test/CircleTest.cpp @@ -29,8 +29,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Circle.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -59,11 +58,12 @@ CircleTest::CircleTest() { } void CircleTest::solid2D() { - Trade::MeshData2D circle = Primitives::circle2DSolid(8); + Trade::MeshData circle = Primitives::circle2DSolid(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 1); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f}, { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, @@ -71,15 +71,15 @@ void CircleTest::solid2D() { { 0.0f, -1.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, { 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 0); } void CircleTest::solid2DTextureCoords() { - Trade::MeshData2D circle = Primitives::circle2DSolid(8, Primitives::CircleTextureCoords::Generate); + Trade::MeshData circle = Primitives::circle2DSolid(8, Primitives::CircleTextureCoords::Generate); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 2); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f}, { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, @@ -87,8 +87,7 @@ void CircleTest::solid2DTextureCoords() { { 0.0f, -1.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, { 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE_AS(circle.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.5f}, {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, @@ -99,11 +98,12 @@ void CircleTest::solid2DTextureCoords() { } void CircleTest::solid3D() { - Trade::MeshData3D circle = Primitives::circle3DSolid(8); + Trade::MeshData circle = Primitives::circle3DSolid(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 2); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, @@ -111,7 +111,7 @@ void CircleTest::solid3D() { { 0.0f, -1.0f, 0.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, { 1.0f, 0.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.normals(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, @@ -123,15 +123,15 @@ void CircleTest::solid3D() { { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 0); } void CircleTest::solid3DTextureCoords() { - Trade::MeshData3D circle = Primitives::circle3DSolid(8, Primitives::CircleTextureCoords::Generate); + Trade::MeshData circle = Primitives::circle3DSolid(8, Primitives::CircleTextureCoords::Generate); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 3); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, @@ -139,7 +139,7 @@ void CircleTest::solid3DTextureCoords() { { 0.0f, -1.0f, 0.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, { 1.0f, 0.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.normals(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, @@ -151,8 +151,7 @@ void CircleTest::solid3DTextureCoords() { { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE_AS(circle.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.5f}, {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, @@ -163,11 +162,12 @@ void CircleTest::solid3DTextureCoords() { } void CircleTest::wireframe2D() { - Trade::MeshData2D circle = Primitives::circle2DWireframe(8); + Trade::MeshData circle = Primitives::circle2DWireframe(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 1); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, {-1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, @@ -176,11 +176,12 @@ void CircleTest::wireframe2D() { } void CircleTest::wireframe3D() { - Trade::MeshData3D circle = Primitives::circle3DWireframe(8); + Trade::MeshData circle = Primitives::circle3DWireframe(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 1); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, diff --git a/src/Magnum/Primitives/Test/ConeTest.cpp b/src/Magnum/Primitives/Test/ConeTest.cpp index bc3e848065..f907f62020 100644 --- a/src/Magnum/Primitives/Test/ConeTest.cpp +++ b/src/Magnum/Primitives/Test/ConeTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Cone.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -51,9 +51,13 @@ ConeTest::ConeTest() { } void ConeTest::solidWithoutAnything() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f); + Trade::MeshData cone = coneSolid(2, 3, 1.0f); - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 2); + + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 1.0f}, /* 0 */ {0.866025f, -1.0f, -0.5f}, /* 1 */ {-0.866025f, -1.0f, -0.5f}, /* 2 */ @@ -67,7 +71,7 @@ void ConeTest::solidWithoutAnything() { {0.0f, 1.0f, 0.0f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.447214f, 0.894427f}, /* 0 */ {0.774597f, 0.447214f, -0.447214f}, /* 1 */ {-0.774597f, 0.447214f, -0.447214f}, /* 2 */ @@ -81,20 +85,22 @@ void ConeTest::solidWithoutAnything() { {-0.774597f, 0.447214f, -0.447214f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cone.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 2, 0, 3, 2, 3, 5, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7, 5, 3, 6, 5, 6, 8 }), TestSuite::Compare::Container); } void ConeTest::solidWithCaps() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::CapEnd); + Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::CapEnd); + + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 2); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 1.0f}, /* 1 */ @@ -114,7 +120,7 @@ void ConeTest::solidWithCaps() { {0.0f, 1.0f, 0.0f}, /* 12 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -134,11 +140,9 @@ void ConeTest::solidWithCaps() { {-0.774597f, 0.447214f, -0.447214f}, /* 12 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cone.hasTextureCoords2D()); - /* Faces of the caps and sides do not share any vertices due to different normals */ - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, 7, 8, 11, 7, 11, 10, 8, 9, 12, 8, 12, 11, 9, 7, 10, 9, 10, 12 @@ -146,11 +150,15 @@ void ConeTest::solidWithCaps() { } void ConeTest::solidWithTextureCoords() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords); + Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords); + + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 1.0f}, /* 0 */ {0.866025f, -1.0f, -0.5f}, /* 1 */ {-0.866025f, -1.0f, -0.5f}, /* 2 */ @@ -167,7 +175,7 @@ void ConeTest::solidWithTextureCoords() { {0.0f, 1.0f, 0.0f} /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.447214f, 0.894427f}, /* 0 */ {0.774597f, 0.447214f, -0.447214f}, /* 1 */ {-0.774597f, 0.447214f, -0.447214f}, /* 2 */ @@ -184,7 +192,7 @@ void ConeTest::solidWithTextureCoords() { {0.0f, 0.447214f, 0.894427f} /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.0f, 0.0f}, /* 0 */ {0.333333f, 0.0f}, /* 1 */ {0.666667f, 0.0f}, /* 2 */ @@ -202,18 +210,22 @@ void ConeTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); /* Each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 1, 5, 0, 5, 4, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 4, 5, 9, 4, 9, 8, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10 }), TestSuite::Compare::Container); } void ConeTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords|ConeFlag::CapEnd); + Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords|ConeFlag::CapEnd); + + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 1.0f}, /* 1 */ @@ -237,7 +249,7 @@ void ConeTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.0f, 0.0f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -261,7 +273,7 @@ void ConeTest::solidWithTextureCoordsAndCaps() { {0.0f, 0.447214f, 0.894427f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, /* 0 */ {0.0f, 0.333333f}, /* 1 */ @@ -287,7 +299,7 @@ void ConeTest::solidWithTextureCoordsAndCaps() { /* Faces of the caps and sides do not share any vertices due to different normals, each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, 7, 8, 12, 7, 12, 11, 9, 10, 14, 9, 14, 13, 10, 11, 15, 10, 15, 14, 11, 12, 16, 11, 16, 15 @@ -295,9 +307,13 @@ void ConeTest::solidWithTextureCoordsAndCaps() { } void ConeTest::wireframe() { - Trade::MeshData3D cone = coneWireframe(8, 1.5f); + Trade::MeshData cone = coneWireframe(8, 1.5f); - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 1); + + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 1.0f}, /* 0 */ {1.0f, -1.5f, 0.0f}, /* 1 */ {0.0f, -1.5f, -1.0f}, /* 2 */ @@ -310,10 +326,7 @@ void ConeTest::wireframe() { {0.0f, 1.5f, 0.0f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cone.hasNormals()); - CORRADE_VERIFY(!cone.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 4, 1, 5, 2, 6, 3, 7, 4, 1, 5, 2, 6, 3, 7, 0, diff --git a/src/Magnum/Primitives/Test/CrosshairTest.cpp b/src/Magnum/Primitives/Test/CrosshairTest.cpp index 8b2474c5a1..fe4ee73cc1 100644 --- a/src/Magnum/Primitives/Test/CrosshairTest.cpp +++ b/src/Magnum/Primitives/Test/CrosshairTest.cpp @@ -28,8 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Crosshair.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -46,19 +45,25 @@ CrosshairTest::CrosshairTest() { } void CrosshairTest::twoDimensions() { - Trade::MeshData2D crosshair = Primitives::crosshair2D(); + Trade::MeshData crosshair = Primitives::crosshair2D(); - CORRADE_VERIFY(!crosshair.isIndexed()); CORRADE_COMPARE(crosshair.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(crosshair.positions(0).size(), 4); + CORRADE_VERIFY(!crosshair.isIndexed()); + CORRADE_COMPARE(crosshair.vertexCount(), 4); + CORRADE_COMPARE(crosshair.attributeCount(), 1); + CORRADE_COMPARE(crosshair.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{0.0f, 1.0f})); } void CrosshairTest::threeDimensions() { - Trade::MeshData3D crosshair = Primitives::crosshair3D(); + Trade::MeshData crosshair = Primitives::crosshair3D(); - CORRADE_VERIFY(!crosshair.isIndexed()); CORRADE_COMPARE(crosshair.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(crosshair.positions(0).size(), 6); + CORRADE_VERIFY(!crosshair.isIndexed()); + CORRADE_COMPARE(crosshair.vertexCount(), 6); + CORRADE_COMPARE(crosshair.attributeCount(), 1); + CORRADE_COMPARE(crosshair.attribute(Trade::MeshAttribute::Position)[4], + (Vector3{ 0.0f, 0.0f, -1.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/CubeTest.cpp b/src/Magnum/Primitives/Test/CubeTest.cpp index 5e4398ea50..3c0c1ad140 100644 --- a/src/Magnum/Primitives/Test/CubeTest.cpp +++ b/src/Magnum/Primitives/Test/CubeTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Cube.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,29 +47,42 @@ CubeTest::CubeTest() { } void CubeTest::solid() { - Trade::MeshData3D cube = Primitives::cubeSolid(); + Trade::MeshData cube = Primitives::cubeSolid(); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::Triangles); - CORRADE_COMPARE(cube.indices().size(), 36); - CORRADE_COMPARE(cube.positions(0).size(), 24); - CORRADE_COMPARE(cube.normals(0).size(), 24); + CORRADE_VERIFY(cube.isIndexed()); + CORRADE_COMPARE(cube.indexCount(), 36); + CORRADE_COMPARE(cube.vertexCount(), 24); + CORRADE_COMPARE(cube.attributeCount(), 2); + CORRADE_COMPARE(cube.indices()[17], 11); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Position)[4], + (Vector3{1.0f, -1.0f, 1.0f})); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Normal)[6], + (Vector3{1.0f, 0.0f, 0.0f})); } void CubeTest::solidStrip() { - Trade::MeshData3D cube = Primitives::cubeSolidStrip(); + Trade::MeshData cube = Primitives::cubeSolidStrip(); - CORRADE_VERIFY(!cube.isIndexed()); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(cube.positions(0).size(), 14); - CORRADE_COMPARE(cube.normalArrayCount(), 0); + CORRADE_VERIFY(!cube.isIndexed()); + CORRADE_COMPARE(cube.vertexCount(), 14); + CORRADE_COMPARE(cube.attributeCount(), 1); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Position)[4], + (Vector3{-1.0f, -1.0f, -1.0f})); } void CubeTest::wireframe() { - Trade::MeshData3D cube = Primitives::cubeWireframe(); + Trade::MeshData cube = Primitives::cubeWireframe(); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(cube.indices().size(), 24); - CORRADE_COMPARE(cube.positions(0).size(), 8); + CORRADE_VERIFY(cube.isIndexed()); + CORRADE_COMPARE(cube.indexCount(), 24); + CORRADE_COMPARE(cube.vertexCount(), 8); + CORRADE_COMPARE(cube.attributeCount(), 1); + CORRADE_COMPARE(cube.indices()[5], 3); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Position)[5], + (Vector3{1.0f, -1.0f, -1.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/CylinderTest.cpp b/src/Magnum/Primitives/Test/CylinderTest.cpp index 93392a2ea9..f1a10e7fe5 100644 --- a/src/Magnum/Primitives/Test/CylinderTest.cpp +++ b/src/Magnum/Primitives/Test/CylinderTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Cylinder.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -51,9 +51,13 @@ CylinderTest::CylinderTest() { } void CylinderTest::solidWithoutAnything() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f); - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 2); + + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 1.0f}, /* 0 */ {0.866025f, -1.5f, -0.5f}, /* 1 */ {-0.866025f, -1.5f, -0.5f}, /* 2 */ @@ -67,7 +71,7 @@ void CylinderTest::solidWithoutAnything() { {-0.866025f, 1.5f, -0.5f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, /* 0 */ {0.866025f, 0.0f, -0.5f}, /* 1 */ {-0.866025f, 0.0f, -0.5f}, /* 2 */ @@ -81,20 +85,22 @@ void CylinderTest::solidWithoutAnything() { {-0.866025f, 0.0f, -0.5f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cylinder.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 2, 0, 3, 2, 3, 5, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7, 5, 3, 6, 5, 6, 8 }), TestSuite::Compare::Container); } void CylinderTest::solidWithCaps() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::CapEnds); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::CapEnds); + + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 2); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, /* 0 */ {0.0f, -1.5f, 1.0f}, /* 1 */ @@ -120,7 +126,7 @@ void CylinderTest::solidWithCaps() { {0.0f, 1.5f, 0.0f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -146,11 +152,9 @@ void CylinderTest::solidWithCaps() { {0.0f, 1.0f, 0.0f}, /* 16 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cylinder.hasTextureCoords2D()); - /* Faces of the caps and sides do not share any vertices due to different normals */ - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, 7, 8, 11, 7, 11, 10, 8, 9, 12, 8, 12, 11, 9, 7, 10, 9, 10, 12, @@ -159,11 +163,15 @@ void CylinderTest::solidWithCaps() { } void CylinderTest::solidWithTextureCoords() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords); + + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 3); /* First vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 1.0f}, /* 0 */ {0.866025f, -1.5f, -0.5f}, /* 1 */ {-0.866025f, -1.5f, -0.5f}, /* 2 */ @@ -180,7 +188,7 @@ void CylinderTest::solidWithTextureCoords() { {0.0f, 1.5f, 1.0f}, /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, /* 0 */ {0.866025f, 0.0f, -0.5f}, /* 1 */ {-0.866025f, 0.0f, -0.5f}, /* 2 */ @@ -197,7 +205,7 @@ void CylinderTest::solidWithTextureCoords() { {0.0f, 0.0f, 1.0f}, /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.0f, 0.0f}, /* 0 */ {0.333333f, 0.0f}, /* 1 */ {0.666667f, 0.0f}, /* 2 */ @@ -215,18 +223,22 @@ void CylinderTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); /* Each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 1, 5, 0, 5, 4, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 4, 5, 9, 4, 9, 8, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10 }), TestSuite::Compare::Container); } void CylinderTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords|CylinderFlag::CapEnds); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords|CylinderFlag::CapEnds); + + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, /* 0 */ {0.0f, -1.5f, 1.0f}, /* 1 */ @@ -257,7 +269,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.5f, 0.0f} /* 21 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -288,7 +300,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.0f, 0.0f}, /* 21 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, /* 0 */ {0.0f, 0.2f}, /* 1 */ @@ -321,7 +333,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { /* Faces of the caps and sides do not share any vertices due to different normals, each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, 7, 8, 12, 7, 12, 11, 9, 10, 14, 9, 14, 13, 10, 11, 15, 10, 15, 14, 11, 12, 16, 11, 16, 15, @@ -330,9 +342,13 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { } void CylinderTest::wireframe() { - Trade::MeshData3D cylinder = cylinderWireframe(2, 8, 0.5f); + Trade::MeshData cylinder = cylinderWireframe(2, 8, 0.5f); - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 1); + + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -0.5f, 1.0f}, /* 0 */ {1.0f, -0.5f, 0.0f}, /* 1 */ {0.0f, -0.5f, -1.0f}, /* 2 */ @@ -361,10 +377,7 @@ void CylinderTest::wireframe() { {-0.707107f, 0.5f, 0.707107f} /* 23 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cylinder.hasNormals()); - CORRADE_VERIFY(!cylinder.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 4, 1, 5, 2, 6, 3, 7, 4, 1, 5, 2, 6, 3, 7, 0, diff --git a/src/Magnum/Primitives/Test/GradientTest.cpp b/src/Magnum/Primitives/Test/GradientTest.cpp index bd56babf3a..b036859d40 100644 --- a/src/Magnum/Primitives/Test/GradientTest.cpp +++ b/src/Magnum/Primitives/Test/GradientTest.cpp @@ -29,8 +29,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Gradient.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Primitives/Square.h" #include "Magnum/Primitives/Plane.h" @@ -62,23 +61,30 @@ using namespace Magnum::Math::Literals; void GradientTest::gradient2D() { /* The corners sould have 0.2, 0.4, 0.6, 0.8 blends */ - Trade::MeshData2D gradient = Primitives::gradient2D( + Trade::MeshData gradient = Primitives::gradient2D( {-1.0f, 2.0f}, {0.2f, 0.6f, 1.0f}, {1.0f, -2.0f}, {0.4f, 1.0f, 0.0f}); + CORRADE_COMPARE(gradient.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!gradient.isIndexed()); + CORRADE_COMPARE(gradient.attributeCount(), 2); + /* Positions should be the same as for a square */ - Trade::MeshData2D square = Primitives::squareSolid(); + Trade::MeshData square = Primitives::squareSolid(); CORRADE_COMPARE(gradient.primitive(), square.primitive()); - CORRADE_COMPARE_AS(gradient.positions(0), square.positions(0), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(gradient.positions(0), (std::vector{ + CORRADE_COMPARE_AS( + gradient.attribute(Trade::MeshAttribute::Position), + square.attribute(Trade::MeshAttribute::Position), + TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, -1.0f}, /* Bottom right */ { 1.0f, 1.0f}, /* Top right */ {-1.0f, -1.0f}, /* Bottom left */ {-1.0f, 1.0f} /* Top left */ }), TestSuite::Compare::Container); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ {0.36f, 0.92f, 0.2f}, /* 80% */ {0.28f, 0.76f, 0.6f}, /* 40% */ {0.32f, 0.84f, 0.4f}, /* 60% */ @@ -87,10 +93,9 @@ void GradientTest::gradient2D() { } void GradientTest::gradient2DHorizontal() { - Trade::MeshData2D gradient = Primitives::gradient2DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient2DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xdeab09_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, @@ -99,10 +104,9 @@ void GradientTest::gradient2DHorizontal() { } void GradientTest::gradient2DVertical() { - Trade::MeshData2D gradient = Primitives::gradient2DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient2DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xfabcde_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, @@ -112,25 +116,37 @@ void GradientTest::gradient2DVertical() { void GradientTest::gradient3D() { /* The corners sould have 0.2, 0.4, 0.6, 0.8 blends */ - Trade::MeshData3D gradient = Primitives::gradient3D( + Trade::MeshData gradient = Primitives::gradient3D( {-1.0f, 2.0f, -1.5f}, {0.2f, 0.6f, 1.0f}, {1.0f, -2.0f, -1.5f}, {0.4f, 1.0f, 0.0f}); + CORRADE_COMPARE(gradient.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!gradient.isIndexed()); + CORRADE_COMPARE(gradient.attributeCount(), 3); + /* Positions should be the same as for a plane */ - Trade::MeshData3D plane = Primitives::planeSolid(); + Trade::MeshData plane = Primitives::planeSolid(); CORRADE_COMPARE(gradient.primitive(), plane.primitive()); - CORRADE_COMPARE_AS(gradient.positions(0), plane.positions(0), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(gradient.positions(0), (std::vector{ + CORRADE_COMPARE_AS( + gradient.attribute(Trade::MeshAttribute::Position), + plane.attribute(Trade::MeshAttribute::Position), + TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, -1.0f, 0.0f}, /* Bottom right */ { 1.0f, 1.0f, 0.0f}, /* Top right */ {-1.0f, -1.0f, 0.0f}, /* Bottom left */ {-1.0f, 1.0f, 0.0f} /* Top left */ }), TestSuite::Compare::Container); - CORRADE_COMPARE(gradient.normalArrayCount(), 1); + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f} + }), TestSuite::Compare::Container); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ {0.36f, 0.92f, 0.2f}, /* 80% */ {0.28f, 0.76f, 0.6f}, /* 40% */ {0.32f, 0.84f, 0.4f}, /* 60% */ @@ -139,10 +155,9 @@ void GradientTest::gradient3D() { } void GradientTest::gradient3DHorizontal() { - Trade::MeshData3D gradient = Primitives::gradient3DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient3DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xdeab09_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, @@ -151,10 +166,9 @@ void GradientTest::gradient3DHorizontal() { } void GradientTest::gradient3DVertical() { - Trade::MeshData3D gradient = Primitives::gradient3DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient3DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xfabcde_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, diff --git a/src/Magnum/Primitives/Test/GridTest.cpp b/src/Magnum/Primitives/Test/GridTest.cpp index f5114d8b8d..71a4a52d86 100644 --- a/src/Magnum/Primitives/Test/GridTest.cpp +++ b/src/Magnum/Primitives/Test/GridTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Grid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,9 +47,13 @@ GridTest::GridTest() { } void GridTest::solid3DWithoutAnything() { - Trade::MeshData3D grid = grid3DSolid({5, 3}, {}); + Trade::MeshData grid = grid3DSolid({5, 3}, {}); - CORRADE_COMPARE_AS(grid.positions(0), (std::vector{ + CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(grid.isIndexed()); + CORRADE_COMPARE(grid.attributeCount(), 1); + + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, {-0.666667f, -1.0f, 0.0f}, {-0.333333f, -1.0f, 0.0f}, @@ -91,10 +95,7 @@ void GridTest::solid3DWithoutAnything() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(grid.normalArrayCount(), 0); - CORRADE_COMPARE(grid.textureCoords2DArrayCount(), 0); - - CORRADE_COMPARE_AS(grid.indices(), (std::vector{ + CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ 0, 8, 7, 0, 1, 8, 1, 9, 8, 1, 2, 9, 2, 10, 9, 2, 3, 10, @@ -126,9 +127,13 @@ void GridTest::solid3DWithoutAnything() { } void GridTest::solid3DWithNormalsAndTextureCoords() { - Trade::MeshData3D grid = grid3DSolid({5, 3}, GridFlag::GenerateNormals|GridFlag::GenerateTextureCoords); + Trade::MeshData grid = grid3DSolid({5, 3}, GridFlag::GenerateNormals|GridFlag::GenerateTextureCoords); - CORRADE_COMPARE_AS(grid.positions(0), (std::vector{ + CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(grid.isIndexed()); + CORRADE_COMPARE(grid.attributeCount(), 3); + + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, {-0.666667f, -1.0f, 0.0f}, {-0.333333f, -1.0f, 0.0f}, @@ -170,7 +175,7 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.normals(0), (std::vector{ + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, @@ -212,7 +217,7 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {0.0f, 0.0f, 1.0f}, }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.0f, 0.0f}, {0.166667f, 0.0f}, {0.333333f, 0.0f}, @@ -254,7 +259,7 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {1.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.indices(), (std::vector{ + CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ 0, 8, 7, 0, 1, 8, 1, 9, 8, 1, 2, 9, 2, 10, 9, 2, 3, 10, @@ -286,9 +291,13 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { } void GridTest::wireframe3D() { - Trade::MeshData3D grid = grid3DWireframe({5, 3}); + Trade::MeshData grid = grid3DWireframe({5, 3}); + + CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(grid.isIndexed()); + CORRADE_COMPARE(grid.attributeCount(), 1); - CORRADE_COMPARE_AS(grid.positions(0), (std::vector{ + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, {-0.666667f, -1.0f, 0.0f}, {-0.333333f, -1.0f, 0.0f}, @@ -330,7 +339,7 @@ void GridTest::wireframe3D() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.indices(), (std::vector{ + CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ 0, 1, 0, 7, 1, 2, 1, 8, 2, 3, 2, 9, diff --git a/src/Magnum/Primitives/Test/IcosphereTest.cpp b/src/Magnum/Primitives/Test/IcosphereTest.cpp index 5f7c3cac5b..87f59ca969 100644 --- a/src/Magnum/Primitives/Test/IcosphereTest.cpp +++ b/src/Magnum/Primitives/Test/IcosphereTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Icosphere.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,25 +47,30 @@ IcosphereTest::IcosphereTest() { } void IcosphereTest::count0() { - Trade::MeshData3D data = Primitives::icosphereSolid(0); - - CORRADE_COMPARE(data.positionArrayCount(), 1); - CORRADE_COMPARE(data.normalArrayCount(), 1); - - CORRADE_COMPARE(data.indices().size(), 60); - CORRADE_COMPARE(data.positions(0).size(), 12); - CORRADE_COMPARE(data.normals(0).size(), 12); + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + + CORRADE_COMPARE(icosphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(icosphere.isIndexed()); + CORRADE_COMPARE(icosphere.indexCount(), 60); + CORRADE_COMPARE(icosphere.vertexCount(), 12); + CORRADE_COMPARE(icosphere.attributeCount(), 2); + CORRADE_COMPARE(icosphere.indices()[18], 9); + CORRADE_COMPARE(icosphere.attribute(Trade::MeshAttribute::Position)[8], + (Vector3{-0.525731f, -0.850651f, 0.0f})); + CORRADE_COMPARE(icosphere.attribute(Trade::MeshAttribute::Normal)[8], + (Vector3{-0.525731f, -0.850651f, 0.0f})); } void IcosphereTest::data1() { /* This also tests the subdivide() and removeDuplicates() mesh tools */ - Trade::MeshData3D data = Primitives::icosphereSolid(1); + Trade::MeshData icosphere = Primitives::icosphereSolid(1); - CORRADE_COMPARE(data.positionArrayCount(), 1); - CORRADE_COMPARE(data.normalArrayCount(), 1); + CORRADE_COMPARE(icosphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(icosphere.isIndexed()); + CORRADE_COMPARE(icosphere.attributeCount(), 2); - CORRADE_COMPARE_AS(data.indices(), (std::vector{ + CORRADE_COMPARE_AS(icosphere.indices(), Containers::arrayView({ 12, 13, 14, 15, 16, 12, 17, 18, 19, 17, 20, 21, 22, 23, 24, 22, 25, 26, 27, 28, 29, 27, 30, 31, 32, 33, 34, 32, 35, 36, 37, 38, 39, 37, 40, 41, 13, 28, 25, 14, 24, 39, 19, 26, 31, 18, 40, 23, 16, 34, 29, 15, 38, 35, @@ -80,7 +85,8 @@ void IcosphereTest::data1() { 16, 29, 16, 7, 34, 29, 34, 9, 7, 15, 35, 15, 1, 38, 35, 38, 0, 3, 30, 20, 30, 9, 33, 20, 33, 8, 4, 21, 41, 21, 8, 36, 41, 36, 0}), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(data.positions(0), (std::vector{ + + CORRADE_COMPARE_AS(icosphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -0.525731f, 0.850651f}, {0.850651f, 0.0f, 0.525731f}, {0.850651f, 0.0f, -0.525731f}, @@ -124,19 +130,20 @@ void IcosphereTest::data1() { {-0.5f, 0.309017f, 0.809017f}, {-0.5f, -0.309017f, 0.809017f}}), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(data.normals(0), data.positions(0), + CORRADE_COMPARE_AS( + icosphere.attribute(Trade::MeshAttribute::Position), + icosphere.attribute(Trade::MeshAttribute::Normal), TestSuite::Compare::Container); } void IcosphereTest::count2() { - Trade::MeshData3D data = Primitives::icosphereSolid(2); - - CORRADE_COMPARE(data.positionArrayCount(), 1); - CORRADE_COMPARE(data.normalArrayCount(), 1); + Trade::MeshData icosphere = Primitives::icosphereSolid(2); - CORRADE_COMPARE(data.indices().size(), 960); - CORRADE_COMPARE(data.positions(0).size(), 162); - CORRADE_COMPARE(data.normals(0).size(), 162); + CORRADE_COMPARE(icosphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(icosphere.isIndexed()); + CORRADE_COMPARE(icosphere.indexCount(), 960); + CORRADE_COMPARE(icosphere.vertexCount(), 162); + CORRADE_COMPARE(icosphere.attributeCount(), 2); } }}}} diff --git a/src/Magnum/Primitives/Test/LineTest.cpp b/src/Magnum/Primitives/Test/LineTest.cpp index 02c0f7972b..d0c80a8a14 100644 --- a/src/Magnum/Primitives/Test/LineTest.cpp +++ b/src/Magnum/Primitives/Test/LineTest.cpp @@ -28,8 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Line.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -46,19 +45,25 @@ LineTest::LineTest() { } void LineTest::twoDimensions() { - Trade::MeshData2D line = Primitives::line2D(); + Trade::MeshData line = Primitives::line2D(); - CORRADE_VERIFY(!line.isIndexed()); CORRADE_COMPARE(line.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(line.positions(0).size(), 2); + CORRADE_VERIFY(!line.isIndexed()); + CORRADE_COMPARE(line.vertexCount(), 2); + CORRADE_COMPARE(line.attributeCount(), 1); + CORRADE_COMPARE(line.attribute(Trade::MeshAttribute::Position)[1], + (Vector2{1.0f, 0.0f})); } void LineTest::threeDimensions() { - Trade::MeshData3D line = Primitives::line3D(); + Trade::MeshData line = Primitives::line3D(); - CORRADE_VERIFY(!line.isIndexed()); CORRADE_COMPARE(line.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(line.positions(0).size(), 2); + CORRADE_VERIFY(!line.isIndexed()); + CORRADE_COMPARE(line.vertexCount(), 2); + CORRADE_COMPARE(line.attributeCount(), 1); + CORRADE_COMPARE(line.attribute(Trade::MeshAttribute::Position)[1], + (Vector3{1.0f, 0.0f, 0.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/PlaneTest.cpp b/src/Magnum/Primitives/Test/PlaneTest.cpp index 14ce8ba948..614b8f4a3d 100644 --- a/src/Magnum/Primitives/Test/PlaneTest.cpp +++ b/src/Magnum/Primitives/Test/PlaneTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Plane.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,32 +47,42 @@ PlaneTest::PlaneTest() { } void PlaneTest::solid() { - Trade::MeshData3D plane = Primitives::planeSolid(); + Trade::MeshData plane = Primitives::planeSolid(); - CORRADE_VERIFY(!plane.isIndexed()); CORRADE_COMPARE(plane.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(plane.positions(0).size(), 4); - CORRADE_COMPARE(plane.normals(0).size(), 4); - CORRADE_COMPARE(plane.textureCoords2DArrayCount(), 0); + CORRADE_VERIFY(!plane.isIndexed()); + CORRADE_COMPARE(plane.vertexCount(), 4); + CORRADE_COMPARE(plane.attributeCount(), 2); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Position)[3], + (Vector3{-1.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Normal)[2], + (Vector3{0.0f, 0.0f, 1.0f})); } void PlaneTest::solidTextured() { - Trade::MeshData3D plane = Primitives::planeSolid(Primitives::PlaneTextureCoords::Generate); + Trade::MeshData plane = Primitives::planeSolid(Primitives::PlaneTextureCoords::Generate); - CORRADE_VERIFY(!plane.isIndexed()); CORRADE_COMPARE(plane.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(plane.positions(0).size(), 4); - CORRADE_COMPARE(plane.normals(0).size(), 4); - CORRADE_COMPARE(plane.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(plane.textureCoords2D(0).size(), 4); + CORRADE_VERIFY(!plane.isIndexed()); + CORRADE_COMPARE(plane.vertexCount(), 4); + CORRADE_COMPARE(plane.attributeCount(), 3); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Position)[3], + (Vector3{-1.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Normal)[2], + (Vector3{0.0f, 0.0f, 1.0f})); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::TextureCoordinates)[1], + (Vector2{1.0f, 1.0f})); } void PlaneTest::wireframe() { - Trade::MeshData3D plane = Primitives::planeWireframe(); + Trade::MeshData plane = Primitives::planeWireframe(); - CORRADE_VERIFY(!plane.isIndexed()); CORRADE_COMPARE(plane.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE(plane.positions(0).size(), 4); + CORRADE_VERIFY(!plane.isIndexed()); + CORRADE_COMPARE(plane.vertexCount(), 4); + CORRADE_COMPARE(plane.attributeCount(), 1); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Position)[3], + (Vector3{-1.0f, 1.0f, 0.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/SquareTest.cpp b/src/Magnum/Primitives/Test/SquareTest.cpp index d4abf8bfce..f06966f5a3 100644 --- a/src/Magnum/Primitives/Test/SquareTest.cpp +++ b/src/Magnum/Primitives/Test/SquareTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector2.h" #include "Magnum/Primitives/Square.h" -#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,30 +47,38 @@ SquareTest::SquareTest() { } void SquareTest::solid() { - Trade::MeshData2D square = Primitives::squareSolid(); + Trade::MeshData square = Primitives::squareSolid(); - CORRADE_VERIFY(!square.isIndexed()); CORRADE_COMPARE(square.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(square.positions(0).size(), 4); - CORRADE_COMPARE(square.textureCoords2DArrayCount(), 0); + CORRADE_VERIFY(!square.isIndexed()); + CORRADE_COMPARE(square.vertexCount(), 4); + CORRADE_COMPARE(square.attributeCount(), 1); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{-1.0f, 1.0f})); } void SquareTest::solidTextured() { - Trade::MeshData2D square = Primitives::squareSolid(Primitives::SquareTextureCoords::Generate); + Trade::MeshData square = Primitives::squareSolid(Primitives::SquareTextureCoords::Generate); - CORRADE_VERIFY(!square.isIndexed()); CORRADE_COMPARE(square.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(square.positions(0).size(), 4); - CORRADE_COMPARE(square.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(square.textureCoords2D(0).size(), 4); + CORRADE_VERIFY(!square.isIndexed()); + CORRADE_COMPARE(square.vertexCount(), 4); + CORRADE_COMPARE(square.attributeCount(), 2); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{-1.0f, 1.0f})); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::TextureCoordinates)[1], + (Vector2{1.0f, 1.0f})); } void SquareTest::wireframe() { - Trade::MeshData2D square = Primitives::squareWireframe(); + Trade::MeshData square = Primitives::squareWireframe(); - CORRADE_VERIFY(!square.isIndexed()); CORRADE_COMPARE(square.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE(square.positions(0).size(), 4); + CORRADE_VERIFY(!square.isIndexed()); + CORRADE_COMPARE(square.vertexCount(), 4); + CORRADE_COMPARE(square.attributeCount(), 1); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{-1.0f, 1.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/UVSphereTest.cpp b/src/Magnum/Primitives/Test/UVSphereTest.cpp index 98efb97b01..33d9ebe3ad 100644 --- a/src/Magnum/Primitives/Test/UVSphereTest.cpp +++ b/src/Magnum/Primitives/Test/UVSphereTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/UVSphere.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,9 +47,13 @@ UVSphereTest::UVSphereTest() { } void UVSphereTest::solidWithoutTextureCoords() { - Trade::MeshData3D sphere = uvSphereSolid(3, 3); + Trade::MeshData sphere = uvSphereSolid(3, 3); - CORRADE_COMPARE_AS(sphere.positions(0), (std::vector{ + CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(sphere.isIndexed()); + CORRADE_COMPARE(sphere.attributeCount(), 2); + + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.5f, 0.866025f}, @@ -63,7 +67,7 @@ void UVSphereTest::solidWithoutTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.normals(0), (std::vector{ + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.5f, 0.866025f}, @@ -77,7 +81,7 @@ void UVSphereTest::solidWithoutTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.indices(), (std::vector{ + CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 5, 1, 5, 4, 2, 3, 6, 2, 6, 5, 3, 1, 4, 3, 4, 6, 4, 5, 7, 5, 6, 7, 6, 4, 7 @@ -85,9 +89,13 @@ void UVSphereTest::solidWithoutTextureCoords() { } void UVSphereTest::solidWithTextureCoords() { - Trade::MeshData3D sphere = uvSphereSolid(3, 3, UVSphereTextureCoords::Generate); + Trade::MeshData sphere = uvSphereSolid(3, 3, UVSphereTextureCoords::Generate); + + CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(sphere.isIndexed()); + CORRADE_COMPARE(sphere.attributeCount(), 3); - CORRADE_COMPARE_AS(sphere.positions(0), (std::vector{ + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.5f, 0.866025f}, @@ -103,7 +111,7 @@ void UVSphereTest::solidWithTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, {0.0f, 0.333333f}, @@ -119,7 +127,7 @@ void UVSphereTest::solidWithTextureCoords() { {0.5f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.indices(), (std::vector{ + CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 3, 4, 8, 3, 8, 7, 5, 6, 9, 6, 7, 9, 7, 8, 9 @@ -127,9 +135,13 @@ void UVSphereTest::solidWithTextureCoords() { } void UVSphereTest::wireframe() { - Trade::MeshData3D sphere = uvSphereWireframe(6, 8); + Trade::MeshData sphere = uvSphereWireframe(6, 8); - CORRADE_COMPARE_AS(sphere.positions(0), (std::vector{ + CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(sphere.isIndexed()); + CORRADE_COMPARE(sphere.attributeCount(), 1); + + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.866025f, 0.5f}, @@ -167,9 +179,7 @@ void UVSphereTest::wireframe() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(sphere.normalArrayCount(), 0); - - CORRADE_COMPARE_AS(sphere.indices(), (std::vector{ + CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 1, 0, 2, 0, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 8, diff --git a/src/Magnum/Primitives/UVSphere.cpp b/src/Magnum/Primitives/UVSphere.cpp index 597310027d..b4434d2516 100644 --- a/src/Magnum/Primitives/UVSphere.cpp +++ b/src/Magnum/Primitives/UVSphere.cpp @@ -29,14 +29,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords) { +Trade::MeshData uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords) { CORRADE_ASSERT(rings >= 2 && segments >= 3, "Primitives::uvSphereSolid(): at least two rings and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid sphere(segments, textureCoords == UVSphereTextureCoords::Generate ? Implementation::Spheroid::TextureCoords::Generate : @@ -62,10 +62,10 @@ Trade::MeshData3D uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSpher return sphere.finalize(); } -Trade::MeshData3D uvSphereWireframe(const UnsignedInt rings, const UnsignedInt segments) { +Trade::MeshData uvSphereWireframe(const UnsignedInt rings, const UnsignedInt segments) { CORRADE_ASSERT(rings >= 2 && rings%2 == 0 && segments >= 4 && segments%4 == 0, "Primitives::uvSphereWireframe(): multiples of 2 rings and multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::WireframeSpheroid sphere(segments/4); diff --git a/src/Magnum/Primitives/UVSphere.h b/src/Magnum/Primitives/UVSphere.h index 24c5959050..ccec5c2d71 100644 --- a/src/Magnum/Primitives/UVSphere.h +++ b/src/Magnum/Primitives/UVSphere.h @@ -52,15 +52,17 @@ enum class UVSphereTextureCoords: UnsignedByte { equal to @cpp 3 @ce. @param textureCoords Whether to generate texture coordinates -Sphere with radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Triangles with -normals and optional 2D texture coordinates. If texture coordinates are +Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions, @ref VertexFormat::Vector3 normals and optional +@ref VertexFormat::Vector2 texture coordinates. If texture coordinates are generated, vertices of one segment are duplicated for texture wrapping. @image html primitives-uvspheresolid.png width=256px @see @ref icosphereSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords = UVSphereTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords = UVSphereTextureCoords::DontGenerate); /** @brief Wireframe 3D UV sphere @@ -69,13 +71,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D uvSphereSolid(UnsignedInt rings, Unsi @param segments Number of (line) segments. Must be larger or equal to @cpp 4 @ce and multiple of @cpp 4 @ce. -Sphere with radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Lines. +Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-uvspherewireframe.png width=256px @see @ref icosphereSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D uvSphereWireframe(UnsignedInt rings, UnsignedInt segments); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData uvSphereWireframe(UnsignedInt rings, UnsignedInt segments); }} diff --git a/src/Magnum/Sampler.h b/src/Magnum/Sampler.h index 369ecc6390..188c3a196f 100644 --- a/src/Magnum/Sampler.h +++ b/src/Magnum/Sampler.h @@ -47,13 +47,17 @@ information about the mapping. @see @ref SamplerMipmap, @ref SamplerWrapping */ enum class SamplerFilter: UnsignedInt { + /* Unlike with MeshIndexType, MeshPrimitive, VertexFormat, PixelFormat + etc., this enum doesn't have zero as an invalid value -- Nearest is a + good default */ + /** * Nearest neighbor filtering. * * Corresponds to @ref GL::SamplerFilter::Nearest / * @def_vk_keyword{FILTER_NEAREST,Filter}. */ - Nearest, + Nearest = 0, /** * Linear interpolation filtering. @@ -77,6 +81,10 @@ each value for more information about the mapping. @see @ref SamplerFilter, @ref SamplerWrapping */ enum class SamplerMipmap: UnsignedInt { + /* Unlike with MeshIndexType, MeshPrimitive, VertexFormat, PixelFormat + etc., this enum doesn't have zero as an invalid value -- Base is a + good default */ + /** * Select base mip level * @@ -85,7 +93,7 @@ enum class SamplerMipmap: UnsignedInt { * @def_vk_keyword{SAMPLER_MIPMAP_MODE_NEAREST,SamplerMipmapMode} and you * have to configure the sampler to use just a single mipmap level. */ - Base, + Base = 0, /** * Select nearest mip level. @@ -119,13 +127,17 @@ presence. @see @ref SamplerFilter, @ref SamplerMipmap */ enum class SamplerWrapping: UnsignedInt { + /* Unlike with MeshIndexType, MeshPrimitive, VertexFormat, PixelFormat + etc., this enum doesn't have zero as an invalid value -- Repeat is a + good default */ + /** * Repeat texture. * * Corresponds to @ref GL::SamplerWrapping::Repeat / * @def_vk_keyword{SAMPLER_ADDRESS_MODE_REPEAT,SamplerAddressMode}. */ - Repeat, + Repeat = 0, /** * Repeat mirrored texture. diff --git a/src/Magnum/Shaders/Generic.h b/src/Magnum/Shaders/Generic.h index dd3bf90332..18f2238823 100644 --- a/src/Magnum/Shaders/Generic.h +++ b/src/Magnum/Shaders/Generic.h @@ -78,21 +78,23 @@ template struct Generic { * @brief Vertex position * * @ref Magnum::Vector2 "Vector2" in 2D and @ref Magnum::Vector3 "Vector3" - * in 3D. + * in 3D. Corresponds to @ref Trade::MeshAttribute::Position. */ typedef GL::Attribute<0, T> Position; /** * @brief 2D texture coordinates * - * @ref Magnum::Vector2 "Vector2". + * @ref Magnum::Vector2 "Vector2". Corresponds to + * @ref Trade::MeshAttribute::TextureCoordinates. */ typedef GL::Attribute<1, Vector2> TextureCoordinates; /** * @brief Vertex normal * - * @ref Magnum::Vector3 "Vector3", defined only in 3D. + * @ref Magnum::Vector3 "Vector3", defined only in 3D. Corresponds to + * @ref Trade::MeshAttribute::Normal. */ typedef GL::Attribute<2, Vector3> Normal; @@ -108,6 +110,7 @@ template struct Generic { * @brief Three-component vertex color. * * @ref Magnum::Color3. Use either this or the @ref Color4 attribute. + * Corresponds to @ref Trade::MeshAttribute::Color. */ typedef GL::Attribute<3, Magnum::Color3> Color3; @@ -115,6 +118,7 @@ template struct Generic { * @brief Four-component vertex color. * * @ref Magnum::Color4. Use either this or the @ref Color3 attribute. + * Corresponds to @ref Trade::MeshAttribute::Color. */ typedef GL::Attribute<3, Magnum::Color4> Color4; diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index 010ece013c..84e7681d7f 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -48,8 +48,7 @@ #include "Magnum/Shaders/DistanceFieldVector.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 799026f36e..e70cf2ef70 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -52,8 +52,7 @@ #include "Magnum/Shaders/Flat.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -640,11 +639,11 @@ template void FlatGLTest::renderVertexColor2D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData2D circleData = Primitives::circle2DSolid(32, + Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::CircleTextureCoords::Generate); /* Highlight a quarter */ - Containers::Array colorData{Containers::DirectInit, circleData.positions(0).size(), 0x999999_rgbf}; + Containers::Array colorData{Containers::DirectInit, circleData.vertexCount(), 0x999999_rgbf}; for(std::size_t i = 8; i != 16; ++i) colorData[i + 1] = 0xffff99_rgbf*1.5f; @@ -694,11 +693,11 @@ template void FlatGLTest::renderVertexColor3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* Highlight the middle rings */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0x999999_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0x999999_rgbf}; for(std::size_t i = 6*33; i != 9*33; ++i) colorData[i + 1] = 0xffff99_rgbf*1.5f; diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index a4326883de..1a574cdfcb 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -51,8 +51,7 @@ #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Shaders/MeshVisualizer.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -409,24 +408,19 @@ void MeshVisualizerGLTest::renderWireframe() { #endif #endif - const Trade::MeshData3D sphereData = Primitives::icosphereSolid(1); + const Trade::MeshData sphereData = Primitives::icosphereSolid(1); GL::Mesh sphere{NoCreate}; if(data.flags & MeshVisualizer::Flag::NoGeometryShader) { - sphere = GL::Mesh{}; - sphere.setCount(sphereData.indices().size()); - /* Duplicate the vertices */ - GL::Buffer positions; - positions.setData(MeshTools::duplicate(Containers::stridedArrayView(sphereData.indices()), Containers::stridedArrayView(sphereData.positions(0)))); - sphere.addVertexBuffer(std::move(positions), 0, MeshVisualizer::Position{}); + sphere = MeshTools::compile(MeshTools::duplicate(sphereData)); /* Supply also the vertex ID, if needed */ #ifndef MAGNUM_TARGET_GLES2 if(!GL::Context::current().isExtensionSupported()) #endif { - Containers::Array vertexIndex{sphereData.indices().size()}; + Containers::Array vertexIndex{sphereData.indexCount()}; std::iota(vertexIndex.begin(), vertexIndex.end(), 0.0f); GL::Buffer vertexId; diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 424861056a..0e5b73d6d4 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -51,8 +51,8 @@ #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Shaders/Phong.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -506,12 +506,12 @@ void PhongGLTest::renderDefaults() { /* The light is at the center by default, so we scale the sphere to half and move the vertices back a bit to avoid a fully-black render but still have the thing in the default [-1; 1] cube */ - Trade::MeshData3D meshData = Primitives::uvSphereSolid(16, 32); + Trade::MeshData meshData = Primitives::uvSphereSolid(16, 32); Matrix4 transformation = Matrix4::translation(Vector3::zAxis(-1.0f))*Matrix4::scaling(Vector3(1.0f, 1.0f, 0.25f)); - MeshTools::transformPointsInPlace(transformation, meshData.positions(0)); + MeshTools::transformPointsInPlace(transformation, meshData.mutableAttribute(Trade::MeshAttribute::Position)); /** @todo use Matrix4::normalMatrix() */ - MeshTools::transformVectorsInPlace(transformation.inverted().transposed(), meshData.normals(0)); + MeshTools::transformVectorsInPlace(transformation.inverted().transposed(), meshData.mutableAttribute(Trade::MeshAttribute::Normal)); GL::Mesh sphere = MeshTools::compile(meshData); Phong shader; @@ -854,11 +854,11 @@ template void PhongGLTest::renderVertexColor() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* Highlight the middle rings */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0x999999_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0x999999_rgbf}; for(std::size_t i = 6*33; i != 9*33; ++i) colorData[i + 1] = 0xffff99_rgbf*1.5f; diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 6cf2abccc5..4abe9c71f2 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -48,8 +48,7 @@ #include "Magnum/Shaders/Vector.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index 3c0dc7e201..85cf56ab5f 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -44,8 +44,7 @@ #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Shaders/VertexColor.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -197,11 +196,11 @@ void VertexColorGLTest::renderTeardown() { template void VertexColorGLTest::renderDefaults2D() { setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); - Trade::MeshData2D circleData = Primitives::circle2DSolid(32, + Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::CircleTextureCoords::Generate); /* All a single color */ - Containers::Array colorData{Containers::DirectInit, circleData.positions(0).size(), 0xffffff_rgbf}; + Containers::Array colorData{Containers::DirectInit, circleData.vertexCount(), 0xffffff_rgbf}; GL::Buffer colors; colors.setData(colorData); @@ -238,11 +237,11 @@ template void VertexColorGLTest::renderDefaults3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* All a single color */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0xffffff_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0xffffff_rgbf}; GL::Buffer colors; colors.setData(colorData); @@ -271,11 +270,11 @@ template void VertexColorGLTest::renderDefaults3D() { template void VertexColorGLTest::render2D() { setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); - Trade::MeshData2D circleData = Primitives::circle2DSolid(32, + Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::CircleTextureCoords::Generate); /* Highlight a quarter */ - Containers::Array colorData{Containers::DirectInit, circleData.positions(0).size(), 0x9999ff_rgbf}; + Containers::Array colorData{Containers::DirectInit, circleData.vertexCount(), 0x9999ff_rgbf}; for(std::size_t i = 8; i != 16; ++i) colorData[i + 1] = 0xffff99_rgbf; @@ -316,11 +315,11 @@ template void VertexColorGLTest::render3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* Highlight the middle rings */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0x9999ff_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0x9999ff_rgbf}; for(std::size_t i = 6*33; i != 9*33; ++i) colorData[i + 1] = 0xffff99_rgbf; diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index c205c80f09..53d29e0b40 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -29,13 +29,11 @@ corrade_add_test(ImageTest ImageTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(ImageViewTest ImageViewTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(MeshTest MeshTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(PixelFormatTest PixelFormatTest.cpp LIBRARIES MagnumTestLib) -target_compile_definitions(PixelFormatTest PRIVATE "CORRADE_GRACEFUL_ASSERT") corrade_add_test(PixelStorageTest PixelStorageTest.cpp LIBRARIES Magnum) corrade_add_test(ResourceManagerTest ResourceManagerTest.cpp LIBRARIES Magnum) -target_compile_definitions(ResourceManagerTest PRIVATE "CORRADE_GRACEFUL_ASSERT") corrade_add_test(SamplerTest SamplerTest.cpp LIBRARIES MagnumTestLib) - corrade_add_test(TagsTest TagsTest.cpp LIBRARIES Magnum) +corrade_add_test(VertexFormatTest VertexFormatTest.cpp LIBRARIES MagnumTestLib) set_target_properties( ArrayTest @@ -47,4 +45,12 @@ set_target_properties( ResourceManagerTest SamplerTest TagsTest + VertexFormatTest PROPERTIES FOLDER "Magnum/Test") + +set_property(TARGET + MeshTest + PixelFormatTest + ResourceManagerTest + VertexFormatTest + APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") diff --git a/src/Magnum/Test/MeshTest.cpp b/src/Magnum/Test/MeshTest.cpp index d212d4bf50..10e2d9bf4b 100644 --- a/src/Magnum/Test/MeshTest.cpp +++ b/src/Magnum/Test/MeshTest.cpp @@ -29,6 +29,7 @@ #include #include "Magnum/Mesh.h" +#include "Magnum/Math/Vector4.h" namespace Magnum { namespace Test { namespace { @@ -38,11 +39,19 @@ struct MeshTest: TestSuite::Tester { void primitiveMapping(); void indexTypeMapping(); + void primitiveIsImplementationSpecific(); + void primitiveWrap(); + void primitiveWrapInvalid(); + void primitiveUnwrap(); + void primitiveUnwrapInvalid(); + void indexTypeSize(); void indexTypeSizeInvalid(); void debugPrimitive(); + void debugPrimitiveImplementationSpecific(); void debugIndexType(); + void configurationPrimitive(); void configurationIndexType(); }; @@ -51,11 +60,19 @@ MeshTest::MeshTest() { addTests({&MeshTest::primitiveMapping, &MeshTest::indexTypeMapping, + &MeshTest::primitiveIsImplementationSpecific, + &MeshTest::primitiveWrap, + &MeshTest::primitiveWrapInvalid, + &MeshTest::primitiveUnwrap, + &MeshTest::primitiveUnwrapInvalid, + &MeshTest::indexTypeSize, &MeshTest::indexTypeSizeInvalid, &MeshTest::debugPrimitive, + &MeshTest::debugPrimitiveImplementationSpecific, &MeshTest::debugIndexType, + &MeshTest::configurationPrimitive, &MeshTest::configurationIndexType}); } @@ -63,8 +80,8 @@ MeshTest::MeshTest() { void MeshTest::primitiveMapping() { /* This goes through the first 8 bits, which should be enough. */ UnsignedInt firstUnhandled = 0xff; - UnsignedInt nextHandled = 0; - for(UnsignedInt i = 0; i <= 0xff; ++i) { + UnsignedInt nextHandled = 1; /* 0 is an invalid primitive */ + for(UnsignedInt i = 1; i <= 0xff; ++i) { const auto primitive = MeshPrimitive(i); /* Each case verifies: - that the entries are ordered by number by comparing a function to @@ -101,8 +118,8 @@ void MeshTest::primitiveMapping() { void MeshTest::indexTypeMapping() { /* This goes through the first 8 bits, which should be enough. */ UnsignedInt firstUnhandled = 0xff; - UnsignedInt nextHandled = 0; - for(UnsignedInt i = 0; i <= 0xff; ++i) { + UnsignedInt nextHandled = 1; /* 0 is an invalid type */ + for(UnsignedInt i = 1; i <= 0xff; ++i) { const auto type = MeshIndexType(i); /* Each case verifies: - that the entries are ordered by number by comparing a function to @@ -136,6 +153,41 @@ void MeshTest::indexTypeMapping() { CORRADE_COMPARE(firstUnhandled, 0xff); } +void MeshTest::primitiveIsImplementationSpecific() { + constexpr bool a = isMeshPrimitiveImplementationSpecific(MeshPrimitive::Lines); + constexpr bool b = isMeshPrimitiveImplementationSpecific(MeshPrimitive(0x8000dead)); + CORRADE_VERIFY(!a); + CORRADE_VERIFY(b); +} + +void MeshTest::primitiveWrap() { + constexpr MeshPrimitive a = meshPrimitiveWrap(0xdead); + CORRADE_COMPARE(UnsignedInt(a), 0x8000dead); +} + +void MeshTest::primitiveWrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + meshPrimitiveWrap(0xdeadbeef); + + CORRADE_COMPARE(out.str(), "meshPrimitiveWrap(): implementation-specific value 0xdeadbeef already wrapped or too large\n"); +} + +void MeshTest::primitiveUnwrap() { + constexpr UnsignedInt a = meshPrimitiveUnwrap(MeshPrimitive(0x8000dead)); + CORRADE_COMPARE(a, 0xdead); +} + +void MeshTest::primitiveUnwrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + meshPrimitiveUnwrap(MeshPrimitive::Triangles); + + CORRADE_COMPARE(out.str(), "meshPrimitiveUnwrap(): MeshPrimitive::Triangles isn't a wrapped implementation-specific value\n"); +} + void MeshTest::indexTypeSize() { CORRADE_COMPARE(meshIndexTypeSize(MeshIndexType::UnsignedByte), 1); CORRADE_COMPARE(meshIndexTypeSize(MeshIndexType::UnsignedShort), 2); @@ -146,21 +198,31 @@ void MeshTest::indexTypeSizeInvalid() { std::ostringstream out; Error redirectError{&out}; - meshIndexTypeSize(MeshIndexType(0xdead)); + meshIndexTypeSize(MeshIndexType{}); + meshIndexTypeSize(MeshIndexType(0xfe)); - CORRADE_COMPARE(out.str(), "meshIndexTypeSize(): invalid type MeshIndexType(0xdead)\n"); + CORRADE_COMPARE(out.str(), + "meshIndexTypeSize(): invalid type MeshIndexType(0x0)\n" + "meshIndexTypeSize(): invalid type MeshIndexType(0xfe)\n"); } void MeshTest::debugPrimitive() { std::ostringstream o; - Debug(&o) << MeshPrimitive::TriangleFan << MeshPrimitive(0xdead); - CORRADE_COMPARE(o.str(), "MeshPrimitive::TriangleFan MeshPrimitive(0xdead)\n"); + Debug(&o) << MeshPrimitive::TriangleFan << MeshPrimitive(0xfe); + CORRADE_COMPARE(o.str(), "MeshPrimitive::TriangleFan MeshPrimitive(0xfe)\n"); +} + +void MeshTest::debugPrimitiveImplementationSpecific() { + std::ostringstream out; + Debug{&out} << meshPrimitiveWrap(0xdead); + + CORRADE_COMPARE(out.str(), "MeshPrimitive::ImplementationSpecific(0xdead)\n"); } void MeshTest::debugIndexType() { std::ostringstream o; - Debug(&o) << MeshIndexType::UnsignedShort << MeshIndexType(0xdead); - CORRADE_COMPARE(o.str(), "MeshIndexType::UnsignedShort MeshIndexType(0xdead)\n"); + Debug(&o) << MeshIndexType::UnsignedShort << MeshIndexType(0xfe); + CORRADE_COMPARE(o.str(), "MeshIndexType::UnsignedShort MeshIndexType(0xfe)\n"); } void MeshTest::configurationPrimitive() { @@ -170,9 +232,13 @@ void MeshTest::configurationPrimitive() { CORRADE_COMPARE(c.value("primitive"), "LineStrip"); CORRADE_COMPARE(c.value("primitive"), MeshPrimitive::LineStrip); + c.setValue("zero", MeshPrimitive{}); + CORRADE_COMPARE(c.value("zero"), ""); + CORRADE_COMPARE(c.value("zero"), MeshPrimitive{}); + c.setValue("invalid", MeshPrimitive(0xdead)); CORRADE_COMPARE(c.value("invalid"), ""); - CORRADE_COMPARE(c.value("invalid"), MeshPrimitive::Points); + CORRADE_COMPARE(c.value("invalid"), MeshPrimitive{}); } void MeshTest::configurationIndexType() { @@ -182,9 +248,13 @@ void MeshTest::configurationIndexType() { CORRADE_COMPARE(c.value("type"), "UnsignedShort"); CORRADE_COMPARE(c.value("type"), MeshIndexType::UnsignedShort); + c.setValue("zero", MeshIndexType{}); + CORRADE_COMPARE(c.value("zero"), ""); + CORRADE_COMPARE(c.value("zero"), MeshIndexType{}); + c.setValue("invalid", MeshIndexType(0xdead)); CORRADE_COMPARE(c.value("invalid"), ""); - CORRADE_COMPARE(c.value("invalid"), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(c.value("invalid"), MeshIndexType{}); } }}} diff --git a/src/Magnum/Test/VertexFormatTest.cpp b/src/Magnum/Test/VertexFormatTest.cpp new file mode 100644 index 0000000000..cefe655199 --- /dev/null +++ b/src/Magnum/Test/VertexFormatTest.cpp @@ -0,0 +1,398 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include +#include +#include + +#include "Magnum/VertexFormat.h" +#include "Magnum/Math/Vector4.h" + +namespace Magnum { namespace Test { namespace { + +struct VertexFormatTest: TestSuite::Tester { + explicit VertexFormatTest(); + + void mapping(); + + void isImplementationSpecific(); + void wrap(); + void wrapInvalid(); + void unwrap(); + void unwrapInvalid(); + void size(); + void sizeInvalid(); + void sizeImplementationSpecific(); + void componentCount(); + void componentCountInvalid(); + void componentCountImplementationSpecific(); + void componentFormat(); + void componentFormatInvalid(); + void componentFormatImplementationSpecific(); + void isNormalized(); + void isNormalizedInvalid(); + void isNormalizedImplementationSpecific(); + + void assemble(); + void assembleRoundtrip(); + void assembleCantNormalize(); + void assembleInvalidComponentCount(); + void assembleImplementationSpecific(); + + void debug(); + void debugImplementationSpecific(); + void configuration(); +}; + +constexpr struct { + VertexFormat componentType; + bool normalized; +} CombineRoundtripData[] { + {VertexFormat::Float, false}, + {VertexFormat::Double, false}, + {VertexFormat::UnsignedByte, false}, + {VertexFormat::UnsignedByte, true}, + {VertexFormat::Byte, false}, + {VertexFormat::Byte, true}, + {VertexFormat::UnsignedShort, false}, + {VertexFormat::UnsignedShort, true}, + {VertexFormat::Short, false}, + {VertexFormat::Short, true}, + {VertexFormat::UnsignedInt, false}, + {VertexFormat::Int, false} +}; + +VertexFormatTest::VertexFormatTest() { + addTests({&VertexFormatTest::mapping, + + &VertexFormatTest::isImplementationSpecific, + &VertexFormatTest::wrap, + &VertexFormatTest::wrapInvalid, + &VertexFormatTest::unwrap, + &VertexFormatTest::unwrapInvalid, + &VertexFormatTest::size, + &VertexFormatTest::sizeInvalid, + &VertexFormatTest::sizeImplementationSpecific, + &VertexFormatTest::componentCount, + &VertexFormatTest::componentCountInvalid, + &VertexFormatTest::componentCountImplementationSpecific, + &VertexFormatTest::componentFormat, + &VertexFormatTest::componentFormatInvalid, + &VertexFormatTest::componentFormatImplementationSpecific, + &VertexFormatTest::isNormalized, + &VertexFormatTest::isNormalizedInvalid, + &VertexFormatTest::isNormalizedImplementationSpecific, + + &VertexFormatTest::assemble}); + + addRepeatedInstancedTests({&VertexFormatTest::assembleRoundtrip}, 4, + Containers::arraySize(CombineRoundtripData)); + + addTests({&VertexFormatTest::assembleCantNormalize, + &VertexFormatTest::assembleInvalidComponentCount, + &VertexFormatTest::assembleImplementationSpecific, + + &VertexFormatTest::debug, + &VertexFormatTest::debugImplementationSpecific, + &VertexFormatTest::configuration}); +} + +void VertexFormatTest::mapping() { + /* This goes through the first 16 bits, which should be enough. Going + through 32 bits takes 8 seconds, too much. */ + UnsignedInt firstUnhandled = 0xffff; + UnsignedInt nextHandled = 1; /* 0 is an invalid type */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto type = VertexFormat(i); + /* Each case verifies: + - that the entries are ordered by number by comparing a function to + expected result (so insertion here is done in proper place) + - that there was no gap (unhandled value inside the range) */ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(type) { + #define _c(type) \ + case VertexFormat::type: \ + CORRADE_COMPARE(Utility::ConfigurationValue::toString(VertexFormat::type, {}), #type); \ + CORRADE_COMPARE(nextHandled, i); \ + CORRADE_COMPARE(firstUnhandled, 0xffff); \ + ++nextHandled; \ + continue; + #include "Magnum/Implementation/vertexFormatMapping.hpp" + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + + /* Not handled by any value, remember -- we might either be at the end + of the enum range (which is okay) or some value might be unhandled + here */ + firstUnhandled = i; + } + + CORRADE_COMPARE(firstUnhandled, 0xffff); +} + +void VertexFormatTest::isImplementationSpecific() { + constexpr bool a = isVertexFormatImplementationSpecific(VertexFormat::Vector2sNormalized); + constexpr bool b = isVertexFormatImplementationSpecific(VertexFormat(0x8000dead)); + CORRADE_VERIFY(!a); + CORRADE_VERIFY(b); +} + +void VertexFormatTest::wrap() { + constexpr VertexFormat a = Magnum::vertexFormatWrap(0xdead); + CORRADE_COMPARE(UnsignedInt(a), 0x8000dead); +} + +void VertexFormatTest::wrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatWrap(0xdeadbeef); + + CORRADE_COMPARE(out.str(), "vertexFormatWrap(): implementation-specific value 0xdeadbeef already wrapped or too large\n"); +} + +void VertexFormatTest::unwrap() { + constexpr UnsignedInt a = Magnum::vertexFormatUnwrap(VertexFormat(0x8000dead)); + CORRADE_COMPARE(a, 0xdead); +} + +void VertexFormatTest::unwrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatUnwrap(VertexFormat::Float); + + CORRADE_COMPARE(out.str(), "vertexFormatUnwrap(): VertexFormat::Float isn't a wrapped implementation-specific value\n"); +} + +void VertexFormatTest::size() { + CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector2), sizeof(Vector2)); + CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector3), sizeof(Vector3)); + CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector4), sizeof(Vector4)); +} + +void VertexFormatTest::sizeInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatSize(VertexFormat{}); + Magnum::vertexFormatSize(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "vertexFormatSize(): invalid format VertexFormat(0x0)\n" + "vertexFormatSize(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::sizeImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + Magnum::vertexFormatSize(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), "vertexFormatSize(): can't determine size of an implementation-specific format 0xdead\n"); +} + +void VertexFormatTest::componentCount() { + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::UnsignedByteNormalized), 1); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector2us), 2); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector3bNormalized), 3); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector4), 4); +} + +void VertexFormatTest::componentCountInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatComponentCount(VertexFormat{}); + Magnum::vertexFormatComponentCount(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "vertexFormatComponentCount(): invalid format VertexFormat(0x0)\n" + "vertexFormatComponentCount(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::componentCountImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + Magnum::vertexFormatComponentCount(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), + "vertexFormatComponentCount(): can't determine component count of an implementation-specific format 0xdead\n"); +} + +void VertexFormatTest::componentFormat() { + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector4), VertexFormat::Float); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3h), VertexFormat::Half); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2d), VertexFormat::Double); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::UnsignedByte), VertexFormat::UnsignedByte); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::UnsignedByteNormalized), VertexFormat::UnsignedByte); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3bNormalized), VertexFormat::Byte); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2us), VertexFormat::UnsignedShort); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2sNormalized), VertexFormat::Short); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2ui), VertexFormat::UnsignedInt); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3i), VertexFormat::Int); +} + +void VertexFormatTest::componentFormatInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatComponentFormat(VertexFormat{}); + Magnum::vertexFormatComponentFormat(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "vertexFormatComponentType(): invalid format VertexFormat(0x0)\n" + "vertexFormatComponentType(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::componentFormatImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + Magnum::vertexFormatComponentFormat(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), + "vertexFormatComponentFormat(): can't determine component format of an implementation-specific format 0xdead\n"); +} + +void VertexFormatTest::isNormalized() { + CORRADE_VERIFY(isVertexFormatNormalized(VertexFormat::UnsignedByteNormalized)); + CORRADE_VERIFY(!isVertexFormatNormalized(VertexFormat::Vector2us)); + CORRADE_VERIFY(isVertexFormatNormalized(VertexFormat::Vector3bNormalized)); + CORRADE_VERIFY(!isVertexFormatNormalized(VertexFormat::Vector4)); +} + +void VertexFormatTest::isNormalizedInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + isVertexFormatNormalized(VertexFormat{}); + isVertexFormatNormalized(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "isVertexFormatNormalized(): invalid format VertexFormat(0x0)\n" + "isVertexFormatNormalized(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::isNormalizedImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + isVertexFormatNormalized(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), + "isVertexFormatNormalized(): can't determine normalization of an implementation-specific format 0xdead\n"); +} + +void VertexFormatTest::assemble() { + CORRADE_COMPARE(vertexFormat(VertexFormat::UnsignedShort, 3, true), + VertexFormat::Vector3usNormalized); + CORRADE_COMPARE(vertexFormat(VertexFormat::Int, 4, false), + VertexFormat::Vector4i); + CORRADE_COMPARE(vertexFormat(VertexFormat::Double, 1, false), + VertexFormat::Double); + CORRADE_COMPARE(vertexFormat(VertexFormat::Byte, 1, true), + VertexFormat::ByteNormalized); + + /* Non-scalar types allowed too, as that makes the internal checking + much simpler than when requiring the type to be scalar non-normalized */ + CORRADE_COMPARE(vertexFormat(VertexFormat::Vector4bNormalized, 2, false), + VertexFormat::Vector2b); + CORRADE_COMPARE(vertexFormat(VertexFormat::Vector3h, 2, false), + VertexFormat::Vector2h); +} + +void VertexFormatTest::assembleRoundtrip() { + auto&& data = CombineRoundtripData[testCaseInstanceId()]; + + std::ostringstream out; + { + Debug d{&out, Debug::Flag::NoNewlineAtTheEnd}; + d << data.componentType; + if(data.normalized) d << Debug::nospace << ", normalized"; + } + setTestCaseDescription(out.str()); + + VertexFormat result = vertexFormat(data.componentType, testCaseRepeatId() + 1, data.normalized); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(result), data.componentType); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(result), testCaseRepeatId() + 1); + CORRADE_COMPARE(isVertexFormatNormalized(result), data.normalized); +} + +void VertexFormatTest::assembleCantNormalize() { + std::ostringstream out; + Error redirectError{&out}; + vertexFormat(VertexFormat::Vector2, 1, true); + CORRADE_COMPARE(out.str(), + "vertexFormat(): VertexFormat::Vector2 can't be made normalized\n"); +} + +void VertexFormatTest::assembleInvalidComponentCount() { + std::ostringstream out; + Error redirectError{&out}; + vertexFormat(VertexFormat::Vector3, 5, false); + CORRADE_COMPARE(out.str(), + "vertexFormat(): invalid component count 5\n"); +} + +void VertexFormatTest::assembleImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + vertexFormat(Magnum::vertexFormatWrap(0xdead), 1, true); + CORRADE_COMPARE(out.str(), + "vertexFormat(): can't assemble a format out of an implementation-specific format 0xdead\n"); +} + +void VertexFormatTest::debug() { + std::ostringstream o; + Debug(&o) << VertexFormat::Vector4 << VertexFormat(0xdead); + CORRADE_COMPARE(o.str(), "VertexFormat::Vector4 VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::debugImplementationSpecific() { + std::ostringstream o; + Debug(&o) << Magnum::vertexFormatWrap(0xdead); + CORRADE_COMPARE(o.str(), "VertexFormat::ImplementationSpecific(0xdead)\n"); +} + +void VertexFormatTest::configuration() { + Utility::Configuration c; + + c.setValue("format", VertexFormat::Vector3); + CORRADE_COMPARE(c.value("format"), "Vector3"); + CORRADE_COMPARE(c.value("format"), VertexFormat::Vector3); + + c.setValue("zero", VertexFormat{}); + CORRADE_COMPARE(c.value("zero"), ""); + CORRADE_COMPARE(c.value("zero"), VertexFormat{}); + + c.setValue("invalid", VertexFormat(0xdead)); + CORRADE_COMPARE(c.value("invalid"), ""); + CORRADE_COMPARE(c.value("invalid"), VertexFormat{}); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Test::VertexFormatTest) diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 3138c40db4..3486743e63 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -35,16 +35,23 @@ #include "Magnum/FileCallback.h" #include "Magnum/Trade/AbstractMaterialData.h" #include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/ArrayAllocator.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/TextureData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT #include "Magnum/Trade/configure.h" #endif @@ -241,6 +248,13 @@ Containers::Optional AbstractImporter::doScene(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::scene(): not implemented", {}); } +Containers::Optional AbstractImporter::scene(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::scene(): no file opened", {}); + const Int id = doSceneForName(name); + if(id == -1) return {}; + return scene(id); /* not doScene(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::animationCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animationCount(): no file opened", {}); return doAnimationCount(); @@ -267,7 +281,10 @@ Containers::Optional AbstractImporter::animation(const UnsignedIn CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {}); Containers::Optional animation = doAnimation(id); - CORRADE_ASSERT(!animation || (!animation->_data.deleter() && !animation->_tracks.deleter()), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!animation || + ((!animation->_data.deleter() || animation->_data.deleter() == Implementation::nonOwnedArrayDeleter || animation->_data.deleter() == ArrayAllocator::deleter) && + (!animation->_tracks.deleter() || animation->_tracks.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter", {}); return animation; } @@ -275,6 +292,13 @@ Containers::Optional AbstractImporter::doAnimation(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::animation(): not implemented", {}); } +Containers::Optional AbstractImporter::animation(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); + const Int id = doAnimationForName(name); + if(id == -1) return {}; + return animation(id); /* not doAnimation(), so we get the checks also */ +} + UnsignedInt AbstractImporter::lightCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::lightCount(): no file opened", {}); return doLightCount(); @@ -307,6 +331,13 @@ Containers::Optional AbstractImporter::doLight(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::light(): not implemented", {}); } +Containers::Optional AbstractImporter::light(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::light(): no file opened", {}); + const Int id = doLightForName(name); + if(id == -1) return {}; + return light(id); /* not doLight(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::cameraCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::cameraCount(): no file opened", {}); return doCameraCount(); @@ -339,6 +370,13 @@ Containers::Optional AbstractImporter::doCamera(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::camera(): not implemented", {}); } +Containers::Optional AbstractImporter::camera(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::camera(): no file opened", {}); + const Int id = doCameraForName(name); + if(id == -1) return {}; + return camera(id); /* not doCamera(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::object2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2DCount(): no file opened", {}); return doObject2DCount(); @@ -371,6 +409,13 @@ Containers::Pointer AbstractImporter::doObject2D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::object2D(): not implemented", {}); } +Containers::Pointer AbstractImporter::object2D(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2D(): no file opened", {}); + const Int id = doObject2DForName(name); + if(id == -1) return {}; + return object2D(id); /* not doObject2D(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::object3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3DCount(): no file opened", {}); return doObject3DCount(); @@ -403,28 +448,128 @@ Containers::Pointer AbstractImporter::doObject3D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::object3D(): not implemented", {}); } +Containers::Pointer AbstractImporter::object3D(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3D(): no file opened", {}); + const Int id = doObject3DForName(name); + if(id == -1) return {}; + return object3D(id); /* not doObject3D(), so we get the range checks also */ +} + +UnsignedInt AbstractImporter::meshCount() const { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshCount(): no file opened", {}); + return doMeshCount(); +} + +UnsignedInt AbstractImporter::doMeshCount() const { return 0; } + +UnsignedInt AbstractImporter::meshLevelCount(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshLevelCount(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::meshLevelCount(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + const UnsignedInt out = doMeshLevelCount(id); + CORRADE_ASSERT(out, "Trade::AbstractImporter::meshLevelCount(): implementation reported zero levels", {}); + return out; +} + +UnsignedInt AbstractImporter::doMeshLevelCount(UnsignedInt) { return 1; } + +Int AbstractImporter::meshForName(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshForName(): no file opened", {}); + return doMeshForName(name); +} + +Int AbstractImporter::doMeshForName(const std::string&) { return -1; } + +std::string AbstractImporter::meshName(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshName(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::meshName(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + return doMeshName(id); +} + +std::string AbstractImporter::doMeshName(UnsignedInt) { return {}; } + +Containers::Optional AbstractImporter::mesh(const UnsignedInt id, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::mesh(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + #ifndef CORRADE_NO_ASSERT + /* Check for the range only if requested level is nonzero, as + meshLevelCount() is expected to return >= 1. This is done to prevent + random assertions and messages from a doMeshLevelCount() to be printed + (which are unlikely, but let's be consistent with what image*D() does). */ + if(level) { + const UnsignedInt levelCount = doMeshLevelCount(id); + CORRADE_ASSERT(levelCount, "Trade::AbstractImporter::mesh(): implementation reported zero levels", {}); + CORRADE_ASSERT(level < levelCount, "Trade::AbstractImporter::mesh(): level" << level << "out of range for" << levelCount << "entries", {}); + } + #endif + Containers::Optional mesh = doMesh(id, level); + CORRADE_ASSERT(!mesh || ( + (!mesh->_indexData.deleter() || mesh->_indexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_indexData.deleter() == ArrayAllocator::deleter) && + (!mesh->_vertexData.deleter() || mesh->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_vertexData.deleter() == ArrayAllocator::deleter) && + (!mesh->_attributes.deleter() || mesh->_attributes.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter", {}); + return mesh; +} + +Containers::Optional AbstractImporter::doMesh(UnsignedInt, UnsignedInt) { + CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh(): not implemented", {}); +} + +Containers::Optional AbstractImporter::mesh(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); + const Int id = doMeshForName(name); + if(id == -1) return {}; + return mesh(id, level); /* not doMesh(), so we get the checks also */ +} + +MeshAttribute AbstractImporter::meshAttributeForName(const std::string& name) { + const MeshAttribute out = doMeshAttributeForName(name); + CORRADE_ASSERT(out == MeshAttribute{} || isMeshAttributeCustom(out), + "Trade::AbstractImporter::meshAttributeForName(): implementation-returned" << out << "is neither custom nor invalid", {}); + return out; +} + +MeshAttribute AbstractImporter::doMeshAttributeForName(const std::string&) { + return {}; +} + +std::string AbstractImporter::meshAttributeName(MeshAttribute name) { + CORRADE_ASSERT(isMeshAttributeCustom(name), + "Trade::AbstractImporter::meshAttributeName():" << name << "is not custom", {}); + return doMeshAttributeName(meshAttributeCustom(name)); +} + +std::string AbstractImporter::doMeshAttributeName(UnsignedShort) { return {}; } + +#ifdef MAGNUM_BUILD_DEPRECATED UnsignedInt AbstractImporter::mesh2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DCount(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh2DCount(); + CORRADE_IGNORE_DEPRECATED_POP } UnsignedInt AbstractImporter::doMesh2DCount() const { return 0; } Int AbstractImporter::mesh2DForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DForName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh2DForName(name); + CORRADE_IGNORE_DEPRECATED_POP } Int AbstractImporter::doMesh2DForName(const std::string&) { return -1; } std::string AbstractImporter::mesh2DName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh2DCount(), "Trade::AbstractImporter::mesh2DName(): index" << id << "out of range for" << doMesh2DCount() << "entries", {}); return doMesh2DName(id); + CORRADE_IGNORE_DEPRECATED_POP } std::string AbstractImporter::doMesh2DName(UnsignedInt) { return {}; } +CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional AbstractImporter::mesh2D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2D(): no file opened", {}); CORRADE_ASSERT(id < doMesh2DCount(), "Trade::AbstractImporter::mesh2D(): index" << id << "out of range for" << doMesh2DCount() << "entries", {}); @@ -434,38 +579,56 @@ Containers::Optional AbstractImporter::mesh2D(const UnsignedInt id) Containers::Optional AbstractImporter::doMesh2D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh2D(): not implemented", {}); } +CORRADE_IGNORE_DEPRECATED_POP UnsignedInt AbstractImporter::mesh3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DCount(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh3DCount(); + CORRADE_IGNORE_DEPRECATED_POP } -UnsignedInt AbstractImporter::doMesh3DCount() const { return 0; } +UnsignedInt AbstractImporter::doMesh3DCount() const { + return doMeshCount(); +} Int AbstractImporter::mesh3DForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DForName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh3DForName(name); + CORRADE_IGNORE_DEPRECATED_POP } -Int AbstractImporter::doMesh3DForName(const std::string&) { return -1; } +Int AbstractImporter::doMesh3DForName(const std::string& name) { + return doMeshForName(name); +} std::string AbstractImporter::mesh3DName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh3DCount(), "Trade::AbstractImporter::mesh3DName(): index" << id << "out of range for" << doMesh3DCount() << "entries", {}); return doMesh3DName(id); + CORRADE_IGNORE_DEPRECATED_POP } -std::string AbstractImporter::doMesh3DName(UnsignedInt) { return {}; } +std::string AbstractImporter::doMesh3DName(const UnsignedInt id) { + return doMeshName(id); +} +CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional AbstractImporter::mesh3D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3D(): no file opened", {}); CORRADE_ASSERT(id < doMesh3DCount(), "Trade::AbstractImporter::mesh3D(): index" << id << "out of range for" << doMesh3DCount() << "entries", {}); return doMesh3D(id); } -Containers::Optional AbstractImporter::doMesh3D(UnsignedInt) { - CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh3D(): not implemented", {}); +Containers::Optional AbstractImporter::doMesh3D(const UnsignedInt id) { + Containers::Optional out = doMesh(id, 0); + if(out) return MeshData3D{*out}; + return Containers::NullOpt; } +CORRADE_IGNORE_DEPRECATED_POP +#endif UnsignedInt AbstractImporter::materialCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::materialCount(): no file opened", {}); @@ -499,6 +662,13 @@ Containers::Pointer AbstractImporter::doMaterial(UnsignedI CORRADE_ASSERT(false, "Trade::AbstractImporter::material(): not implemented", {}); } +Containers::Pointer AbstractImporter::material(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::material(): no file opened", {}); + const Int id = doMaterialForName(name); + if(id == -1) return {}; + return material(id); /* not doMaterial(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::textureCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::textureCount(): no file opened", {}); return doTextureCount(); @@ -531,6 +701,13 @@ Containers::Optional AbstractImporter::doTexture(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::texture(): not implemented", {}); } +Containers::Optional AbstractImporter::texture(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::texture(): no file opened", {}); + const Int id = doTextureForName(name); + if(id == -1) return {}; + return texture(id); /* not doTexture(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::image1DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image1DCount(): no file opened", {}); return doImage1DCount(); @@ -579,7 +756,7 @@ Containers::Optional AbstractImporter::image1D(const UnsignedInt id } #endif Containers::Optional image = doImage1D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter || image->_data.deleter() == ArrayAllocator::deleter, "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -587,6 +764,14 @@ Containers::Optional AbstractImporter::doImage1D(UnsignedInt, Unsig CORRADE_ASSERT(false, "Trade::AbstractImporter::image1D(): not implemented", {}); } +Containers::Optional AbstractImporter::image1D(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image1D(): no file opened", {}); + const Int id = doImage1DForName(name); + if(id == -1) return {}; + /* not doImage1D(), so we get the range checks also */ + return image1D(id, level); +} + UnsignedInt AbstractImporter::image2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image2DCount(): no file opened", {}); return doImage2DCount(); @@ -635,7 +820,7 @@ Containers::Optional AbstractImporter::image2D(const UnsignedInt id } #endif Containers::Optional image = doImage2D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter || image->_data.deleter() == ArrayAllocator::deleter, "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -643,6 +828,14 @@ Containers::Optional AbstractImporter::doImage2D(UnsignedInt, Unsig CORRADE_ASSERT(false, "Trade::AbstractImporter::image2D(): not implemented", {}); } +Containers::Optional AbstractImporter::image2D(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image2D(): no file opened", {}); + const Int id = doImage2DForName(name); + if(id == -1) return {}; + /* not doImage2D(), so we get the range checks also */ + return image2D(id, level); +} + UnsignedInt AbstractImporter::image3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image3DCount(): no file opened", {}); return doImage3DCount(); @@ -691,7 +884,7 @@ Containers::Optional AbstractImporter::image3D(const UnsignedInt id } #endif Containers::Optional image = doImage3D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter || image->_data.deleter() == ArrayAllocator::deleter, "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -699,6 +892,14 @@ Containers::Optional AbstractImporter::doImage3D(UnsignedInt, Unsig CORRADE_ASSERT(false, "Trade::AbstractImporter::image3D(): not implemented", {}); } +Containers::Optional AbstractImporter::image3D(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image3D(): no file opened", {}); + const Int id = doImage3DForName(name); + if(id == -1) return {}; + /* not doImage3D(), so we get the range checks also */ + return image3D(id, level); +} + const void* AbstractImporter::importerState() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::importerState(): no file opened", {}); return doImporterState(); diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 5900873c6a..8e0fe8881e 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -159,8 +159,8 @@ expose internal state through various accessors: imported by @ref image1D(), @ref image2D() or @ref image3D() - @ref LightData::importerState() can expose importer state for a light imported by @ref light() -- @ref MeshData3D::importerState() can expose importer state for a mesh - imported by @ref mesh2D() or @ref mesh3D() +- @ref MeshData::importerState() can expose importer state for a mesh + imported by @ref mesh() - @ref ObjectData3D::importerState() can expose importer state for an object imported by @ref object2D() or @ref object3D() - @ref SceneData::importerState() can expose importer state for a scene @@ -194,9 +194,11 @@ dependency on the importer instance and neither on the dynamic plugin module. In other words, you don't need to keep the importer instance (or the plugin manager instance) around in order to have the `*Data` instances valid. Moreover, all @ref Corrade::Containers::Array instances returned through -@ref ImageData, @ref AnimationData and others are only allowed to have default -deleters --- this is to avoid potential dangling function pointer calls when -destructing such instances after the plugin module has been unloaded. +@ref ImageData, @ref AnimationData and @ref MeshData are only allowed to have +default deleters (or be non-owning instances created from +@ref Corrade::Containers::ArrayView) --- this is to avoid potential dangling +function pointer calls when destructing such instances after the plugin module +has been unloaded. The only exception are various `importerState()` functions @ref Trade-AbstractImporter-usage-state "described above", but in that case the @@ -243,9 +245,9 @@ checked by the implementation: there is any file opened. - All `do*()` implementations taking data ID as parameter are called only if the ID is from valid range. -- For `doImage*()` and @p level parameter being nonzero, implementations are - called only if it is from valid range. Level zero is always expected to be - present and thus no check is done in that case. +- For @ref doMesh() and `doImage*()` and @p level parameter being nonzero, + implementations are called only if it is from valid range. Level zero is + always expected to be present and thus no check is done in that case. @m_class{m-block m-warning} @@ -253,9 +255,10 @@ checked by the implementation: As @ref Trade-AbstractImporter-data-dependency "mentioned above", @ref Corrade::Containers::Array instances returned from plugin implementations are not allowed to use anything else than the default - deleter, otherwise this could cause dangling function pointer call on array - destruction if the plugin gets unloaded before the array is destroyed. This - is asserted by the base implementation on return. + deleter or the deleter used by @ref Trade::ArrayAllocator, otherwise this + could cause dangling function pointer call on array destruction if the + plugin gets unloaded before the array is destroyed. This is asserted by the + base implementation on return. @par Similarly for interpolator functions passed through @ref Animation::TrackView instances to @ref AnimationData --- to avoid @@ -488,7 +491,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no scene for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref sceneName() + * @see @ref sceneName(), @ref scene(const std::string&) */ Int sceneForName(const std::string& name); @@ -507,9 +510,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given scene or @ref Containers::NullOpt if import failed. * Expects that a file is opened. + * @see @ref scene(const std::string&) */ Containers::Optional scene(UnsignedInt id); + /** + * @brief Scene for given name + * @m_since_latest + * + * A convenience API combining @ref sceneForName() and + * @ref scene(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref sceneForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional scene(const std::string& name); + /** * @brief Animation count * @@ -522,7 +537,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no animation for given name exists, returns @cpp -1 @ce. Expects * that a file is opened. - * @see @ref animationName() + * @see @ref animationName(), @ref animation(const std::string&) */ Int animationForName(const std::string& name); @@ -541,9 +556,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given animation or @ref Containers::NullOpt if importing * failed. Expects that a file is opened. + * @see @ref animation(const std::string&) */ Containers::Optional animation(UnsignedInt id); + /** + * @brief Animation for given name + * @m_since_latest + * + * A convenience API combining @ref animationForName() and + * @ref animation(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref animationForName() returns @cpp -1 @ce or if importing + * fails. Expects that a file is opened. + */ + Containers::Optional animation(const std::string& name); + /** * @brief Light count * @@ -556,7 +583,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no light for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref lightName() + * @see @ref lightName(), @ref light(const std::string&) */ Int lightForName(const std::string& name); @@ -575,9 +602,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given light or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref light(const std::string&) */ Containers::Optional light(UnsignedInt id); + /** + * @brief Light for given name + * @m_since_latest + * + * A convenience API combining @ref lightForName() and + * @ref light(UnsignedInt). Returns @ref Containers::NullOpt either if + * @ref lightForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional light(const std::string& name); + /** * @brief Camera count * @@ -590,7 +629,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no camera for given name exists, returns @cpp -1 @ce. Expects * that a file is opened. - * @see @ref cameraName() + * @see @ref cameraName(), @ref camera(const std::string&) */ Int cameraForName(const std::string& name); @@ -609,9 +648,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given camera or @ref Containers::NullOpt if importing * failed. Expects that a file is opened. + * @see @ref camera(const std::string&) */ Containers::Optional camera(UnsignedInt id); + /** + * @brief Camera for given name + * @m_since_latest + * + * A convenience API combining @ref cameraForName() and + * @ref camera(UnsignedInt). Returns @ref Containers::NullOpt either if + * @ref cameraForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional camera(const std::string& name); + /** * @brief Two-dimensional object count * @@ -624,7 +675,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no scene for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref object2DName() + * @see @ref object2DName(), @ref object2D(const std::string&) */ Int object2DForName(const std::string& name); @@ -643,9 +694,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given object or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @see @ref object2D(const std::string&) */ Containers::Pointer object2D(UnsignedInt id); + /** + * @brief Two-dimensional object for given name + * @m_since_latest + * + * A convenience API combining @ref object2DForName() and + * @ref object2D(UnsignedInt). Returns @cpp nullptr @ce either if + * @ref object2DForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Pointer object2D(const std::string& name); + /** * @brief Three-dimensional object count * @@ -658,7 +721,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no scene for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref object3DName() + * @see @ref object3DName(), @ref object3D(const std::string&) */ Int object3DForName(const std::string& name); @@ -677,33 +740,144 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given object or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @see @ref object3D(const std::string&) */ Containers::Pointer object3D(UnsignedInt id); + /** + * @brief Three-dimensional object for given name + * @m_since_latest + * + * A convenience API combining @ref object3DForName() and + * @ref object3D(UnsignedInt). Returns @cpp nullptr @ce either if + * @ref object3DForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Pointer object3D(const std::string& name); + + /** + * @brief Mesh count + * @m_since_latest + * + * Expects that a file is opened. + * @see @ref meshLevelCount() + */ + UnsignedInt meshCount() const; + + /** + * @brief Mesh level count + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Always returns at least one level, import failures are deferred to + * @ref mesh(). Expects that a file is opened. + */ + UnsignedInt meshLevelCount(UnsignedInt id); + + /** + * @brief Mesh ID for given name + * @m_since_latest + * + * If no mesh for given name exists, returns @cpp -1 @ce. Expects that + * a file is opened. + * @see @ref meshName(), @ref mesh(const std::string&, UnsignedInt) + */ + Int meshForName(const std::string& name); + + /** + * @brief Mesh name + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Expects that a file is opened. + * @see @ref meshForName() + */ + std::string meshName(UnsignedInt id); + + /** + * @brief Mesh + * @param id Mesh ID, from range [0, @ref meshCount()). + * @param level Mesh level, from range [0, @ref meshLevelCount()) + * @m_since_latest + * + * Returns given mesh or @ref Containers::NullOpt if importing failed. + * The @p level parameter allows access to additional data and is + * largely left as importer-specific --- for example allowing access to + * per-instance, per-face or per-edge data. Expects that a file is + * opened. + * @see @ref mesh(const std::string&, UnsignedInt), + * @ref MeshPrimitive::Instances, @ref MeshPrimitive::Faces, + * @ref MeshPrimitive::Edges + */ + Containers::Optional mesh(UnsignedInt id, UnsignedInt level = 0); + + /** + * @brief Mesh for given name + * @m_since_latest + * + * A convenience API combining @ref meshForName() and + * @ref mesh(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref meshForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional mesh(const std::string& name, UnsignedInt level = 0); + + /** + * @brief Mesh attribute for given name + * @m_since_latest + * + * If the name is not recognized, returns a zero (invalid) + * @ref MeshAttribute, otherwise returns a custom mesh attribute. Note + * that the value returned by this function may depend on whether a + * file is opened or not and also be different for different files --- + * see documentation of a particular importer for more information. + * @see @ref isMeshAttributeCustom() + */ + MeshAttribute meshAttributeForName(const std::string& name); + + /** + * @brief String name for given custom mesh attribute + * @m_since_latest + * + * Given a custom @p name returned by @ref mesh() in a @ref MeshData, + * returns a string identifier. If a string representation is not + * available or @p name is not recognized, returns an empty string. + * Expects that @p name is custom. Note that the value returned by + * this function may depend on whether a file is opened or not and also + * be different for different files --- see documentation of a + * particular importer for more information. + * @see @ref isMeshAttributeCustom() + */ + std::string meshAttributeName(MeshAttribute name); + + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Two-dimensional mesh count * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshCount() instead. */ - UnsignedInt mesh2DCount() const; + CORRADE_DEPRECATED("use meshCount() instead") UnsignedInt mesh2DCount() const; /** * @brief Two-dimensional mesh ID for given name * * If no mesh for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. + * @m_deprecated_since_latest Use @ref meshForName() instead. * @see @ref mesh2DName() */ - Int mesh2DForName(const std::string& name); + CORRADE_DEPRECATED("use meshForName() instead") Int mesh2DForName(const std::string& name); /** * @brief Two-dimensional mesh name * @param id Mesh ID, from range [0, @ref mesh2DCount()). * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshName() instead. * @see @ref mesh2DForName() */ - std::string mesh2DName(UnsignedInt id); + CORRADE_DEPRECATED("use meshName() instead") std::string mesh2DName(UnsignedInt id); /** * @brief Two-dimensional mesh @@ -711,29 +885,39 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given mesh or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref mesh() instead. */ - Containers::Optional mesh2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("use mesh() instead") Containers::Optional mesh2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP - /** @brief Three-dimensional mesh count */ - UnsignedInt mesh3DCount() const; + /** + * @brief Three-dimensional mesh count + * + * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshCount() instead. + */ + CORRADE_DEPRECATED("use meshCount() instead") UnsignedInt mesh3DCount() const; /** * @brief Three-dimensional mesh ID for given name * * If no mesh for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. + * @m_deprecated_since_latest Use @ref meshForName() instead. * @see @ref mesh3DName() */ - Int mesh3DForName(const std::string& name); + CORRADE_DEPRECATED("use meshForName() instead") Int mesh3DForName(const std::string& name); /** * @brief Three-dimensional mesh name * @param id Mesh ID, from range [0, @ref mesh3DCount()). * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshName() instead. * @see @ref mesh3DForName() */ - std::string mesh3DName(UnsignedInt id); + CORRADE_DEPRECATED("use meshName() instead") std::string mesh3DName(UnsignedInt id); /** * @brief Three-dimensional mesh @@ -741,8 +925,12 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given mesh or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshName() instead. */ - Containers::Optional mesh3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("use mesh() instead") Containers::Optional mesh3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP + #endif /** * @brief Material count @@ -765,7 +953,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @param id Material ID, from range [0, @ref materialCount()). * * Expects that a file is opened. - * @see @ref materialForName() + * @see @ref materialForName(), @ref material(const std::string&) */ std::string materialName(UnsignedInt id); @@ -775,9 +963,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given material or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @see @ref material(const std::string&) */ Containers::Pointer material(UnsignedInt id); + /** + * @brief Material for given name + * @m_since_latest + * + * A convenience API combining @ref materialForName() and + * @ref material(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref materialForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Pointer material(const std::string& name); + /** * @brief Texture count * @@ -790,7 +990,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no texture for given name exists, returns @cpp -1 @ce. Expects * that a file is opened. - * @see @ref textureName() + * @see @ref textureName(), @ref texture(const std::string&) */ Int textureForName(const std::string& name); @@ -809,9 +1009,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given texture or @ref Containers::NullOpt if importing * failed. Expects that a file is opened. + * @see @ref texture(const std::string&) */ Containers::Optional texture(UnsignedInt id); + /** + * @brief Texture for given name + * @m_since_latest + * + * A convenience API combining @ref textureForName() and + * @ref texture(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref textureForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional texture(const std::string& name); + /** * @brief One-dimensional image count * @@ -835,7 +1047,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no image for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref image1DName() + * @see @ref image1DName(), @ref image1D(const std::string&, UnsignedInt) */ Int image1DForName(const std::string& name); @@ -855,9 +1067,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given image or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref image1D(const std::string&, UnsignedInt) */ Containers::Optional image1D(UnsignedInt id, UnsignedInt level = 0); + /** + * @brief One-dimensional image for given name + * @m_since_latest + * + * A convenience API combining @ref image1DForName() and + * @ref image1D(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref image1DForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional image1D(const std::string& name, UnsignedInt level = 0); + /** * @brief Two-dimensional image count * @@ -881,7 +1105,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no image for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref image2DName() + * @see @ref image2DName(), @ref image2D(const std::string&, UnsignedInt) */ Int image2DForName(const std::string& name); @@ -901,9 +1125,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given image or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref image2D(const std::string&, UnsignedInt) */ Containers::Optional image2D(UnsignedInt id, UnsignedInt level = 0); + /** + * @brief Two-dimensional image for given name + * @m_since_latest + * + * A convenience API combining @ref image2DForName() and + * @ref image2D(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref image2DForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional image2D(const std::string& name, UnsignedInt level = 0); + /** * @brief Three-dimensional image count * @@ -927,7 +1163,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no image for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref image3DName() + * @see @ref image3DName(), @ref image3D(const std::string&, UnsignedInt) */ Int image3DForName(const std::string& name); @@ -947,9 +1183,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given image or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref image3D(const std::string&, UnsignedInt) */ Containers::Optional image3D(UnsignedInt id, UnsignedInt level = 0); + /** + * @brief Three-dimensional image for given name + * @m_since_latest + * + * A convenience API combining @ref image3DForName() and + * @ref image3D(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref image3DForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional image3D(const std::string& name, UnsignedInt level = 0); + /*@}*/ /** @@ -962,7 +1210,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @see @ref AbstractMaterialData::importerState(), * @ref AnimationData::importerState(), @ref CameraData::importerState(), * @ref ImageData::importerState(), @ref LightData::importerState(), - * @ref MeshData2D::importerState(), @ref MeshData3D::importerState(), + * @ref MeshData::importerState(), * @ref ObjectData2D::importerState(), @ref ObjectData3D::importerState(), * @ref SceneData::importerState(), @ref TextureData::importerState() */ @@ -1165,52 +1413,178 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi virtual Containers::Pointer doObject3D(UnsignedInt id); /** - * @brief Implementation for @ref mesh2DCount() + * @brief Implementation for @ref meshCount() + * @m_since_latest * * Default implementation returns @cpp 0 @ce. */ + virtual UnsignedInt doMeshCount() const; + + /** + * @brief Implementation for @ref meshLevelCount() + * @m_since_latest + * + * Default implementation returns @cpp 1 @ce. Similarly to all other + * `*Count()` functions, this function isn't expected to fail --- if an + * import error occus, this function should return @cpp 1 @ce and the + * error state should be returned from @ref mesh() instead. + * + * Deliberately not @cpp const @ce to allow plugins cache decoded + * data. + */ + virtual UnsignedInt doMeshLevelCount(UnsignedInt id); + + /** + * @brief Implementation for @ref meshForName() + * @m_since_latest + * + * Default implementation returns @cpp -1 @ce. + */ + virtual Int doMeshForName(const std::string& name); + + /** + * @brief Implementation for @ref meshName() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual std::string doMeshName(UnsignedInt id); + + /** + * @brief Implementation for @ref mesh() + * @m_since_latest + */ + virtual Containers::Optional doMesh(UnsignedInt id, UnsignedInt level); + + /** + * @brief Implementation for @ref meshAttributeForName() + * @m_since_latest + * + * Default implementation returns an invalid (zero) value. + */ + virtual MeshAttribute doMeshAttributeForName(const std::string& name); + + /** + * @brief Implementation for @ref meshAttributeName() + * @m_since_latest + * + * Receives the custom ID extracted via @ref meshAttributeCustom(MeshAttribute). + * Default implementation returns an empty string. + */ + virtual std::string doMeshAttributeName(UnsignedShort name); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Implementation for @ref mesh2DCount() + * + * Default implementation returns @cpp 0 @ce. There weren't any + * importers in existence known to implement 2D mesh import, so unlike + * @ref doMesh3DCount() this function doesn't delegate to + * @ref doMeshCount(). + * @m_deprecated_since_latest Implement @ref doMeshCount() instead. + */ + /* MSVC warns when overriding such methods and there's no way to + suppress that warning, making the RT build (which treats deprecation + warnings as errors) fail and other builds extremely noisy. So + disabling those on MSVC. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshCount() instead") + #endif virtual UnsignedInt doMesh2DCount() const; /** * @brief Implementation for @ref mesh2DForName() * - * Default implementation returns @cpp -1 @ce. + * Default implementation returns @cpp -1 @ce. There weren't any + * importers in existence known to implement 2D mesh import, so unlike + * @ref doMesh3DForName() this function doesn't delegate to + * @ref doMeshForName(). + * @m_deprecated_since_latest Implement @ref doMeshForName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshForName() instead") + #endif /* See above */ virtual Int doMesh2DForName(const std::string& name); /** * @brief Implementation for @ref mesh2DName() * - * Default implementation returns empty string. + * Default implementation returns empty string. There weren't any + * importers in existence known to implement 2D mesh import, so unlike + * @ref doMesh3DName() this function doesn't delegate to + * @ref doMeshName(). + * @m_deprecated_since_latest Implement @ref doMeshName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshName() instead") + #endif /* See above */ virtual std::string doMesh2DName(UnsignedInt id); - /** @brief Implementation for @ref mesh2D() */ + /** + * @brief Implementation for @ref mesh2D() + * + * There weren't any importers in existence known to implement 2D mesh + * import, so unlike @ref doMesh3D() this function doesn't + * delegate to @ref doMesh(). + * @m_deprecated_since_latest Implement @ref doMesh() instead. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMesh() instead") + #endif /* See above */ virtual Containers::Optional doMesh2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Implementation for @ref mesh3DCount() * - * Default implementation returns @cpp 0 @ce. + * Default implementation returns @ref doMeshCount() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doMeshCount() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshCount() instead") + #endif /* See above */ virtual UnsignedInt doMesh3DCount() const; /** * @brief Implementation for @ref mesh3DForName() * - * Default implementation returns @cpp -1 @ce. + * Default implementation returns @ref doMeshForName() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doMeshForName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshForName() instead") + #endif /* See above */ virtual Int doMesh3DForName(const std::string& name); /** * @brief Implementation for @ref mesh3DName() * - * Default implementation returns empty string. + * Default implementation returns @ref doMeshName() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doMeshName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshName() instead") + #endif /* See above */ virtual std::string doMesh3DName(UnsignedInt id); - /** @brief Implementation for @ref mesh3D() */ + /** + * @brief Implementation for @ref mesh3D() + * + * Default implementation returns @ref doMesh() converted to + * @cpp MeshData3D @ce for backwards compatibility. + * @m_deprecated_since_latest Implement @ref doMesh() instead. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMesh() instead") + #endif /* See above */ virtual Containers::Optional doMesh3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP + #endif /** * @brief Implementation for @ref materialCount() diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index e63a1be592..d315a0bef7 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -29,12 +29,23 @@ #include "Magnum/Math/Vector4.h" #include "Magnum/Math/Quaternion.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" namespace Magnum { namespace Trade { -AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: _duration{duration}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} {} +AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _duration{duration}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} {} -AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState) noexcept: _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} { +AnimationData::AnimationData(Containers::Array&& data, std::initializer_list tracks, const Range1D& duration, const void* importerState): AnimationData{std::move(data), Implementation::initializerListToArrayWithDefaultDeleter(tracks), duration, importerState} {} + +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: AnimationData{Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(tracks), duration, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::AnimationData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, std::initializer_list tracks, const Range1D& duration, const void* importerState): AnimationData{dataFlags, data, Implementation::initializerListToArrayWithDefaultDeleter(tracks), duration, importerState} {} + +AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} { if(!_tracks.empty()) { /* Reset duration to duration of the first track so it properly support cases where tracks don't start at 0 */ @@ -44,12 +55,28 @@ AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& data, std::initializer_list tracks, const void* importerState): AnimationData{std::move(data), Implementation::initializerListToArrayWithDefaultDeleter(tracks), importerState} {} + +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& tracks, const void* importerState) noexcept: AnimationData{Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(tracks), importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::AnimationData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, std::initializer_list tracks, const void* importerState): AnimationData{dataFlags, data, Implementation::initializerListToArrayWithDefaultDeleter(tracks), importerState} {} + AnimationData::~AnimationData() = default; AnimationData::AnimationData(AnimationData&&) noexcept = default; AnimationData& AnimationData::operator=(AnimationData&&) noexcept = default; +Containers::ArrayView AnimationData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::AnimationData::mutableData(): the animation is not mutable", {}); + return _data; +} + AnimationTrackType AnimationData::trackType(UnsignedInt id) const { CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::trackType(): index out of range", {}); return _tracks[id]._type; @@ -70,11 +97,23 @@ UnsignedInt AnimationData::trackTarget(UnsignedInt id) const { return _tracks[id]._target; } -const Animation::TrackViewStorage& AnimationData::track(UnsignedInt id) const { +const Animation::TrackViewStorage& AnimationData::track(UnsignedInt id) const { CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::track(): index out of range", _tracks[id]._view); return _tracks[id]._view; } +const Animation::TrackViewStorage& AnimationData::mutableTrack(UnsignedInt id) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::AnimationData::mutableTrack(): the animation is not mutable", reinterpret_cast&>(_tracks[id]._view)); + CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::track(): index out of range", reinterpret_cast&>(_tracks[id]._view)); + return reinterpret_cast&>(_tracks[id]._view); +} + +Containers::Array AnimationData::release() { + _tracks = nullptr; + return std::move(_data); +} + template auto animationInterpolatorFor(Animation::Interpolation interpolation) -> R(*)(const V&, const V&, Float) { return Animation::interpolatorFor(interpolation); } diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 698c3439b5..50f7b51ded 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -32,6 +32,7 @@ #include "Magnum/Magnum.h" #include "Magnum/Math/Math.h" #include "Magnum/Animation/Track.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" @@ -227,24 +228,36 @@ class AnimationTrackData { * initialization of the track array for @ref AnimationData, expected * to be replaced with concrete values later. */ - /*implicit*/ AnimationTrackData() noexcept: _type{}, _resultType{}, _targetType{}, _target{}, _view{} {} + explicit AnimationTrackData() noexcept: _type{}, _resultType{}, _targetType{}, _target{}, _view{} {} /** - * @brief Constructor + * @brief Type-erased constructor * @param type Value type * @param resultType Result type * @param targetType Track target type * @param target Track target * @param view Type-erased @ref Animation::TrackView instance */ - /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} + explicit AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} /** @overload * * Equivalent to the above with @p type used as both value type and * result type. */ - /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} + explicit AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} + + /** + * @brief Constructor + * @param targetType Track target type + * @param target Track target + * @param view @ref Animation::TrackView instance + * @m_since_latest + * + * Detects @ref AnimationTrackType from @p view type and delegates to + * @ref AnimationTrackData(AnimationTrackType, AnimationTrackType, AnimationTrackTargetType, UnsignedInt, Animation::TrackViewStorage). + */ + template explicit AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept; private: friend AnimationData; @@ -252,7 +265,7 @@ class AnimationTrackData { AnimationTrackType _type, _resultType; AnimationTrackTargetType _targetType; UnsignedInt _target; - Animation::TrackViewStorage _view; + Animation::TrackViewStorage _view; }; /** @@ -278,12 +291,26 @@ array is then updated during calls to @ref Animation::Player::advance(). It's also possible to directly update object transformations using callbacks, among other things. See documentation of the @ref Animation::Player class for more information. + +@section Trade-AnimationData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained +keyframe data through the @ref data() and @ref track() accessors. This is done +because in general case the data can also refer to a memory-mapped file or +constant memory. In cases when it's desirable to modify the data in-place, +there's the @ref mutableData() and @ref mutableTrack() set of functions. To use +these, you need to check that the data are mutable using @ref dataFlags() +first. The following snippet inverts the Y coordinate of a translation +animation: + +@snippet MagnumTrade.cpp AnimationData-usage-mutable + @experimental */ class MAGNUM_TRADE_EXPORT AnimationData { public: /** - * @brief Construct with implicit duration + * @brief Construct an animation data * @param data Buffer containing all keyframe data for this * animation clip * @param tracks Track data @@ -292,11 +319,47 @@ class MAGNUM_TRADE_EXPORT AnimationData { * Each item of @p track should have an @ref Animation::TrackView * instance pointing its key/value views to @p data. The @ref duration() * is automatically calculated from durations of all tracks. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref AnimationData(DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor instead. */ explicit AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState = nullptr) noexcept; /** - * @brief Construct with explicit duration + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(Containers::Array&& data, std::initializer_list tracks, const void* importerState = nullptr); + + /** + * @brief Construct a non-owned animation data + * @param dataFlags Data flags + * @param data View on a buffer containing all keyframe data + * for this animation clip + * @param tracks Track data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref AnimationData(Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& tracks, const void* importerState = nullptr) noexcept; + + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, std::initializer_list tracks, const void* importerState = nullptr); + + /** + * @brief Construct an animation data with explicit duration * @param data Buffer containing all keyframe data for this * animation clip * @param tracks Track data @@ -305,9 +368,46 @@ class MAGNUM_TRADE_EXPORT AnimationData { * * Each item of @p track should have an @ref Animation::TrackView * instance pointing its key/value views to @p data. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref AnimationData(DataFlags, Containers::ArrayView, Containers::Array&&, const Range1D&, const void*) + * constructor instead. */ explicit AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState = nullptr) noexcept; + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(Containers::Array&& data, std::initializer_list tracks, const Range1D& duration, const void* importerState = nullptr); + + /** + * @brief Construct a non-owned animation data with explicit duration + * @param dataFlags Data flags + * @param data View on a buffer containing all keyframe data + * for this animation clip + * @param tracks Track data + * @param duration Animation track duration + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref AnimationData(Containers::Array&&, Containers::Array&&, const Range1D&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& tracks, const Range1D& duration, const void* importerState = nullptr) noexcept; + + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, std::initializer_list tracks, const Range1D& duration, const void* importerState = nullptr); + ~AnimationData(); /** @brief Copying is not allowed */ @@ -322,18 +422,40 @@ class MAGNUM_TRADE_EXPORT AnimationData { /** @brief Move assignment */ AnimationData& operator=(AnimationData&&) noexcept; + /** + * @brief Data flags + * @m_since_latest + * + * @see @ref release(), @ref mutableData(), @ref mutableTrack() + */ + DataFlags dataFlags() const { return _dataFlags; } + /** * @brief Raw data * * Contains data for all tracks contained in this clip. - * @see @ref release() + * @see @ref release(), @ref mutableData() */ - Containers::ArrayView data() & { return _data; } - Containers::ArrayView data() && = delete; /**< @overload */ - - /** @overload */ Containers::ArrayView data() const & { return _data; } - Containers::ArrayView data() const && = delete; /**< @overload */ + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView data() const && = delete; + + /** + * @brief Mutable raw data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * animation is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView mutableData() && = delete; /** @brief Duration */ Range1D duration() const { return _duration; } @@ -400,7 +522,17 @@ class MAGNUM_TRADE_EXPORT AnimationData { * checked version below to access a concrete @ref Animation::TrackView * type. */ - const Animation::TrackViewStorage& track(UnsignedInt id) const; + const Animation::TrackViewStorage& track(UnsignedInt id) const; + + /** + * @brief Mutable track data storage + * @m_since_latest + * + * Like @ref track(), but returns a mutable view. Expects that the + * animation is mutable. + * @see @ref dataFlags() + */ + const Animation::TrackViewStorage& mutableTrack(UnsignedInt id); /** * @brief Track data @@ -414,16 +546,29 @@ class MAGNUM_TRADE_EXPORT AnimationData { * use the view or you need to release the data array using * @ref release() and manage its lifetime yourself. */ - template> const Animation::TrackView& track(UnsignedInt id) const; + template> const Animation::TrackView& track(UnsignedInt id) const; + + /** + * @brief Mutable track data + * @m_since_latest + * + * Like @ref track(), but returns a mutable view. Expects that the + * animation is mutable. + * @see @ref dataFlags() + */ + template> const Animation::TrackView& mutableTrack(UnsignedInt id); /** * @brief Release data storage * * Releases the ownership of the data array and resets internal state - * to default. - * @see @ref data() + * to default. The animation then behaves like it's empty. Note that + * the returned array has a custom no-op deleter when the data are not + * owned by the animation, and while the returned array type is + * mutable, the actual memory might be not. + * @see @ref data(), @ref dataFlags() */ - Containers::Array release() { return std::move(_data); } + Containers::Array release(); /** * @brief Importer-specific state @@ -438,6 +583,7 @@ class MAGNUM_TRADE_EXPORT AnimationData { implementations. */ friend AbstractImporter; + DataFlags _dataFlags; Range1D _duration; Containers::Array _data; Containers::Array _tracks; @@ -505,10 +651,19 @@ namespace Implementation { } #endif -template const Animation::TrackView& AnimationData::track(UnsignedInt id) const { - const Animation::TrackViewStorage& storage = track(id); - CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::track(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); - CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._resultType, "Trade::AnimationData::track(): improper result type requested for" << _tracks[id]._resultType, (static_cast&>(storage))); +template inline AnimationTrackData::AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept: AnimationTrackData{Implementation::animationTypeFor(), Implementation::animationTypeFor(), targetType, target, view} {} + +template const Animation::TrackView& AnimationData::track(UnsignedInt id) const { + const Animation::TrackViewStorage& storage = track(id); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::track(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._resultType, "Trade::AnimationData::track(): improper result type requested for" << _tracks[id]._resultType, (static_cast&>(storage))); + return static_cast&>(storage); +} + +template const Animation::TrackView& AnimationData::mutableTrack(UnsignedInt id) { + const Animation::TrackViewStorage& storage = mutableTrack(id); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::mutableTrack(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._resultType, "Trade::AnimationData::mutableTrack(): improper result type requested for" << _tracks[id]._resultType, (static_cast&>(storage))); return static_cast&>(storage); } diff --git a/src/Magnum/Trade/ArrayAllocator.cpp b/src/Magnum/Trade/ArrayAllocator.cpp new file mode 100644 index 0000000000..cc574e884d --- /dev/null +++ b/src/Magnum/Trade/ArrayAllocator.cpp @@ -0,0 +1,34 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "ArrayAllocator.h" + +namespace Magnum { namespace Trade { + +void ArrayAllocator::deleter(char* const data, std::size_t) { + deallocate(data); +} + +}} diff --git a/src/Magnum/Trade/ArrayAllocator.h b/src/Magnum/Trade/ArrayAllocator.h new file mode 100644 index 0000000000..29426effc9 --- /dev/null +++ b/src/Magnum/Trade/ArrayAllocator.h @@ -0,0 +1,60 @@ +#ifndef Magnum_Trade_ArrayAllocator_h +#define Magnum_Trade_ArrayAllocator_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::ArrayAllocator + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Growable array allocator to be used in importer plugins +@m_since_latest + +Compared to @ref Corrade::Containers::ArrayMallocAllocator ensures that the +@ref Array deleter function pointer is defined in the @ref Trade library and +not in the plugin binary itself, avoiding dangling function pointer call when +the data array is destructed after the plugin has been unloaded. Other than +that the behavior is identical. +*/ +template struct ArrayAllocator: Containers::ArrayMallocAllocator {}; + +#ifndef DOXYGEN_GENERATING_OUTPUT +template<> struct ArrayAllocator: Containers::ArrayMallocAllocator { + MAGNUM_TRADE_EXPORT static void deleter(char* data, std::size_t size); +}; +#endif + +}} + +#endif diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 89d5163136..09a34b12ca 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -27,9 +27,9 @@ find_package(Corrade REQUIRED PluginManager) set(MagnumTrade_SRCS AbstractMaterialData.cpp + ArrayAllocator.cpp + Data.cpp LightData.cpp - MeshData2D.cpp - MeshData3D.cpp MeshObjectData2D.cpp MeshObjectData3D.cpp SceneData.cpp @@ -41,6 +41,7 @@ set(MagnumTrade_GracefulAssert_SRCS AnimationData.cpp CameraData.cpp ImageData.cpp + MeshData.cpp ObjectData2D.cpp ObjectData3D.cpp PhongMaterialData.cpp) @@ -50,11 +51,12 @@ set(MagnumTrade_HEADERS AbstractImageConverter.h AbstractMaterialData.h AnimationData.h + ArrayAllocator.h CameraData.h + Data.h ImageData.h LightData.h - MeshData2D.h - MeshData3D.h + MeshData.h MeshObjectData2D.h MeshObjectData3D.h ObjectData2D.h @@ -66,6 +68,25 @@ set(MagnumTrade_HEADERS visibility.h) +set(MagnumTrade_PRIVATE_HEADERS + Implementation/arrayUtilities.h) + +if(MAGNUM_BUILD_DEPRECATED) + list(APPEND MagnumTrade_GracefulAssert_SRCS + # These have to be here instead of in MagnumTrade_SRCS because they + # include MeshData.h and call (and thus instantiate) various functions + # with inline asserts. We need the linker to pick the variant with + # graceful asserts for tests, and if there would be two different + # copies, it may happen it picks the non-graceful-assert variant, + # causing the tests to blow up. Happens only on the MSVC linker, but + # let's be safe and do this everywhere. + MeshData2D.cpp + MeshData3D.cpp) + list(APPEND MagnumTrade_HEADERS + MeshData2D.h + MeshData3D.h) +endif() + if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/configure.h) @@ -74,7 +95,8 @@ endif() # Objects shared between main and test library add_library(MagnumTradeObjects OBJECT ${MagnumTrade_SRCS} - ${MagnumTrade_HEADERS}) + ${MagnumTrade_HEADERS} + ${MagnumTrade_PRIVATE_HEADERS}) target_include_directories(MagnumTradeObjects PUBLIC $ $) diff --git a/src/Magnum/Trade/Data.cpp b/src/Magnum/Trade/Data.cpp new file mode 100644 index 0000000000..b7efc41c89 --- /dev/null +++ b/src/Magnum/Trade/Data.cpp @@ -0,0 +1,57 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "Data.h" + +#include + +namespace Magnum { namespace Trade { + +Debug& operator<<(Debug& debug, const DataFlag value) { + debug << "Trade::DataFlag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case DataFlag::v: return debug << "::" #v; + _c(Owned) + _c(Mutable) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const DataFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::DataFlags{}", { + DataFlag::Owned, + DataFlag::Mutable}); +} + +namespace Implementation { + void nonOwnedArrayDeleter(char*, std::size_t) { /* does nothing */ } +} + +}} diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h new file mode 100644 index 0000000000..59d98e922d --- /dev/null +++ b/src/Magnum/Trade/Data.h @@ -0,0 +1,95 @@ +#ifndef Magnum_Trade_Data_h +#define Magnum_Trade_Data_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Enum @ref Magnum::Trade::DataFlag, enum set @ref Magnum::Trade::DataFlags + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Data flag +@m_since_latest + +@see @ref DataFlags, @ref AnimationData::dataFlags(), + @ref ImageData::dataFlags(), @ref MeshData::indexDataFlags(), + @ref MeshData::vertexDataFlags() +*/ +enum class DataFlag: UnsignedByte { + /** + * Data are owned by the instance. If this flag is not set, the instance + * might be for example referencing a memory-mapped file or a constant + * memory. + */ + Owned = 1 << 0, + + /** + * Data are mutable. If this flag is not set, the instance might be for + * example referencing a readonly memory-mapped file or a constant memory. + */ + Mutable = 2 << 0 + + /** @todo owned by importer, owned by the GPU, ... */ +}; + +/** +@debugoperatorenum{DataFlag} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); + +/** +@brief Data flags +@m_since_latest + +@see @ref AnimationData::dataFlags(), @ref ImageData::dataFlags(), + @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() +*/ +typedef Containers::EnumSet DataFlags; + +CORRADE_ENUMSET_OPERATORS(DataFlags) + +/** +@debugoperatorenum{DataFlags} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value); + +namespace Implementation { + /* Used internally by MeshData */ + MAGNUM_TRADE_EXPORT void nonOwnedArrayDeleter(char*, std::size_t); +} + +}} + +#endif diff --git a/src/Magnum/Trade/ImageData.cpp b/src/Magnum/Trade/ImageData.cpp index b390fd3570..8b2a2c43f3 100644 --- a/src/Magnum/Trade/ImageData.cpp +++ b/src/Magnum/Trade/ImageData.cpp @@ -35,17 +35,59 @@ namespace Magnum { namespace Trade { template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, format, {}, Magnum::pixelSize(format), size, std::move(data), importerState} {} +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + +template ImageData::ImageData(const PixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + template ImageData::ImageData(const PixelStorage storage, const UnsignedInt format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, pixelFormatWrap(format), formatExtra, pixelSize, size, std::move(data), importerState} {} -template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _compressed{false}, _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _size{size}, _data{std::move(data)}, _importerState{importerState} { +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{false}, _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _size{size}, _data{std::move(data)}, _importerState{importerState} { CORRADE_ASSERT(Magnum::Implementation::imageDataSize(*this) <= _data.size(), "Trade::ImageData: data too small, got" << _data.size() << "but expected at least" << Magnum::Implementation::imageDataSize(*this) << "bytes", ); } -template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _compressed{true}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{std::move(data)}, _importerState{importerState} {} +template ImageData::ImageData(const PixelStorage storage, const UnsignedInt format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, pixelFormatWrap(format), formatExtra, pixelSize, size, dataFlags, data, importerState} {} + +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, const DataFlags dataFlags, Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, formatExtra, pixelSize, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{true}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{std::move(data)}, _importerState{importerState} {} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), size, std::move(data), importerState} {} -template ImageData::ImageData(ImageData&& other) noexcept: _compressed{std::move(other._compressed)}, _size{std::move(other._size)}, _data{std::move(other._data)}, _importerState{std::move(other._importerState)} { +template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(ImageData&& other) noexcept: _dataFlags{other._dataFlags}, _compressed{std::move(other._compressed)}, _size{std::move(other._size)}, _data{std::move(other._data)}, _importerState{std::move(other._importerState)} { if(_compressed) { new(&_compressedStorage) CompressedPixelStorage{std::move(other._compressedStorage)}; _compressedFormat = std::move(other._compressedFormat); @@ -66,6 +108,7 @@ template ImageData::ImageData(ImageData ImageData& ImageData::operator=(ImageData&& other) noexcept { using std::swap; + swap(_dataFlags, other._dataFlags); swap(_compressed, other._compressed); if(_compressed) { swap(_compressedStorage, other._compressedStorage); @@ -115,22 +158,25 @@ template UnsignedInt ImageData::pixelSize() template std::pair, VectorTypeFor> ImageData::dataProperties() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::dataProperties(): the image is compressed", {}); - return Implementation::imageDataProperties(*this); + return Magnum::Implementation::imageDataProperties(*this); } -template Containers::StridedArrayView ImageData::pixels() { - CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); - return Implementation::imagePixelView(*this); +template Containers::ArrayView ImageData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData::mutableData(): the image is not mutable", {}); + return _data; } template Containers::StridedArrayView ImageData::pixels() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); - return Implementation::imagePixelView(*this); + return Magnum::Implementation::imagePixelView(*this, data()); } -template ImageData::operator BasicMutableImageView() { - CORRADE_ASSERT(!_compressed, "Trade::ImageData: the image is compressed", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); - return BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; +template Containers::StridedArrayView ImageData::mutablePixels() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData::mutablePixels(): the image is not mutable", {}); + CORRADE_ASSERT(!_compressed, "Trade::ImageData::mutablePixels(): the image is compressed", {}); + return Magnum::Implementation::imagePixelView(*this, mutableData()); } template ImageData::operator BasicImageView() const { @@ -138,11 +184,11 @@ template ImageData::operator BasicImageView< return BasicImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; } -template ImageData::operator BasicMutableCompressedImageView() { - CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); - return BasicMutableCompressedImageView{ - _compressedStorage, - _compressedFormat, _size, _data}; +template ImageData::operator BasicMutableImageView() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData: the image is not mutable", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); + CORRADE_ASSERT(!_compressed, "Trade::ImageData: the image is compressed", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); + return BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; } template ImageData::operator BasicCompressedImageView() const { @@ -152,6 +198,16 @@ template ImageData::operator BasicCompressed _compressedFormat, _size, _data}; } +template ImageData::operator BasicMutableCompressedImageView() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData: the image is not mutable", + (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); + CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); + return BasicMutableCompressedImageView{ + _compressedStorage, + _compressedFormat, _size, _data}; +} + template Containers::Array ImageData::release() { Containers::Array data{std::move(_data)}; _size = {}; diff --git a/src/Magnum/Trade/ImageData.h b/src/Magnum/Trade/ImageData.h index ea025c0bd9..7caaf4c9ee 100644 --- a/src/Magnum/Trade/ImageData.h +++ b/src/Magnum/Trade/ImageData.h @@ -33,6 +33,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/PixelStorage.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" @@ -87,6 +88,18 @@ compressed properties through @ref compressedStorage() and @snippet MagnumTrade.cpp ImageData-usage +@section Trade-ImageData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained +pixel data through the @ref data() and @ref pixels() accessors. This is done +because in general case the data can also refer to a memory-mapped file or +constant memory. In cases when it's desirable to modify the data in-place, +there's the @ref mutableData() and @ref mutablePixels() set of functions. To +use these, you need to check that the data are mutable using @ref dataFlags() +first. The following snippet flips the R and B channels of the imported image: + +@snippet MagnumTrade.cpp ImageData-usage-mutable + @see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D, @ref Image-pixel-views */ @@ -97,7 +110,7 @@ template class ImageData { }; /** - * @brief Construct uncompressed image data + * @brief Construct an uncompressed image data * @param storage Storage of pixel data * @param format Format of pixel data * @param size Image size @@ -106,11 +119,34 @@ template class ImageData { * * The @p data array is expected to be of proper size for given * parameters. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, DataFlags, Containers::ArrayView, const void*) + * constructor instead. */ explicit ImageData(PixelStorage storage, PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data + * @brief Construct a non-owned uncompressed image data + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelStorage storage, PixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data * @param format Format of pixel data * @param size Image size * @param data Image data @@ -119,10 +155,27 @@ template class ImageData { * Equivalent to calling @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) * with default-constructed @ref PixelStorage. */ - explicit ImageData(PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + explicit ImageData(PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned uncompressed image data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param formatExtra Additional pixel format specifier @@ -139,7 +192,10 @@ template class ImageData { * @ref Magnum::PixelFormat "PixelFormat". * * The @p data array is expected to be of proper size for given - * parameters. + * parameters. The @ref dataFlags() are implicitly set to a combination + * of @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned + * data use the @ref ImageData(PixelStorage, UnsignedInt, UnsignedInt, UnsignedInt, const VectorTypeFor&, DataFlags, Containers::ArrayView, const void*) + * constructor instead. */ explicit ImageData(PixelStorage storage, UnsignedInt format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; @@ -151,7 +207,35 @@ template class ImageData { explicit ImageData(PixelStorage storage, PixelFormat format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param formatExtra Additional pixel format specifier + * @param pixelSize Size of a pixel in given format + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, UnsignedInt, UnsignedInt, UnsignedInt, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelStorage storage, UnsignedInt format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref pixelFormatWrap(). + */ + explicit ImageData(PixelStorage storage, PixelFormat format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param formatExtra Additional pixel format specifier @@ -166,7 +250,26 @@ template class ImageData { template explicit ImageData(PixelStorage storage, T format, U formatExtra, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param formatExtra Additional pixel format specifier + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, T, U, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(PixelStorage storage, T format, U formatExtra, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param size Image size @@ -180,7 +283,25 @@ template class ImageData { template explicit ImageData(PixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data view on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, T, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(PixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data * @param size Image size @@ -190,7 +311,25 @@ template class ImageData { explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a non-owned compressed image data + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data * @param format Format of compressed pixel data * @param size Image size * @param data Image data @@ -199,10 +338,27 @@ template class ImageData { * Equivalent to calling @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) * with default-constructed @ref CompressedPixelStorage. */ - explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned compressed image data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a compressed image data * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data * @param size Image size @@ -214,6 +370,24 @@ template class ImageData { */ template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + /** + * @brief Construct a non-owned compressed image data + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelStorage, T, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + /** * @brief Construct from existing data with attached importer state * @@ -237,6 +411,12 @@ template class ImageData { /** @brief Move assignment */ ImageData& operator=(ImageData&& other) noexcept; + /** + * @brief Data flags + * @m_since_latest + */ + DataFlags dataFlags() const { return _dataFlags; } + /** @brief Whether the image is compressed */ bool isCompressed() const { return _compressed; } @@ -253,6 +433,9 @@ template class ImageData { /** * @brief Conversion to a mutable view * @m_since{2019,10} + * + * The image is expected to be uncompressed and mutable. + * @see @ref isCompressed(), @ref dataFlags() */ /*implicit*/ operator BasicMutableImageView(); @@ -269,6 +452,9 @@ template class ImageData { /** * @brief Conversion to a mutable compressed view * @m_since{2019,10} + * + * The image is expected to be compressed and mutable. + * @see @ref isCompressed(), @ref dataFlags() */ /*implicit*/ operator BasicMutableCompressedImageView(); @@ -358,9 +544,6 @@ template class ImageData { * * @see @ref release(), @ref pixels() */ - Containers::ArrayView data() & { return _data; } - - /** @overload */ Containers::ArrayView data() const & { return _data; } /** @@ -369,7 +552,9 @@ template class ImageData { * * Unlike @ref data(), which returns a view, this is equivalent to * @ref release() to avoid a dangling view when the temporary instance - * goes out of scope. + * goes out of scope. Note that the returned array has a custom no-op + * deleter when the data are not owned by the image, and while the + * returned array type is mutable, the actual memory might be not. * @todoc stupid doxygen can't link to & overloads ffs */ Containers::Array data() && { return release(); } @@ -380,6 +565,16 @@ template class ImageData { */ Containers::Array data() const && = delete; + /** + * @brief Mutable image data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * image is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Image data in a particular type @@ -414,8 +609,17 @@ template class ImageData { * @see @ref isCompressed(), * @ref Corrade::Containers::StridedArrayView::isContiguous() */ - Containers::StridedArrayView pixels(); - Containers::StridedArrayView pixels() const; /**< @overload */ + Containers::StridedArrayView pixels() const; + + /** + * @brief Mutable view on pixel data + * @m_since_latest + * + * Like @ref pixels() const, but returns a non-const view. Expects that + * the image is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView mutablePixels(); /** * @brief View on pixel data with a concrete pixel type @@ -426,26 +630,35 @@ template class ImageData { * correct type for given @ref format() --- checking it on the library * side is not possible for the general case. */ - template Containers::StridedArrayView pixels() { + template Containers::StridedArrayView pixels() const { /* Deliberately not adding a StridedArrayView include, it should work without since this is a templated function and we declare arrayCast() above to satisfy two-phase lookup. */ - return Containers::arrayCast(pixels()); + return Containers::arrayCast(pixels()); } /** - * @overload + * @brief Mutable view on pixel data with a concrete pixel type * @m_since{2019,10} + * + * Like @ref pixels() const, but returns a non-const view. Expects that + * the image is mutable. + * @see @ref dataFlags() */ - template Containers::StridedArrayView pixels() const { - return Containers::arrayCast(pixels()); + template Containers::StridedArrayView mutablePixels() { + /* Deliberately not adding a StridedArrayView include, it should + work without since this is a templated function */ + return Containers::arrayCast(mutablePixels()); } /** * @brief Release data storage * * Releases the ownership of the data array and resets internal state - * to default. + * to default. The image then behaves like it's empty. Note that + * the returned array has a custom no-op deleter when the data are not + * owned by the image, and while the returned array type is mutable, + * the actual memory might be not. * @see @ref data() */ Containers::Array release(); @@ -465,6 +678,9 @@ template class ImageData { explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + DataFlags _dataFlags; bool _compressed; union { PixelStorage _storage; @@ -495,16 +711,31 @@ template template ImageData template ImageData::ImageData(const PixelStorage storage, const T format, const U formatExtra, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), UnsignedInt(formatExtra), Magnum::Implementation::pixelSizeAdl(format, formatExtra), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4 && sizeof(U) <= 4, + "format types larger than 32bits are not supported"); +} + template template ImageData::ImageData(const PixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), {}, Magnum::Implementation::pixelSizeAdl(format), size, std::move(data), importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } +template template ImageData::ImageData(const PixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), {}, Magnum::Implementation::pixelSizeAdl(format), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4, + "format types larger than 32bits are not supported"); +} + template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, std::move(data), importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } +template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4, + "format types larger than 32bits are not supported"); +} + }} #endif diff --git a/src/Magnum/Trade/Implementation/arrayUtilities.h b/src/Magnum/Trade/Implementation/arrayUtilities.h new file mode 100644 index 0000000000..4ae2efd2a0 --- /dev/null +++ b/src/Magnum/Trade/Implementation/arrayUtilities.h @@ -0,0 +1,49 @@ +#ifndef Magnum_Trade_Implementation_arrayUtilities_h +#define Magnum_Trade_Implementation_arrayUtilities_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include + +#include "Magnum/Magnum.h" + +namespace Magnum { namespace Trade { namespace Implementation { + +/* Can't use InPlaceInit as that uses custom deleters. Compared to InPlaceInit + it does an an unnecessary default-initialization of all elements. */ +/** @todo isn't there some C++56 feature that would allow me to allocate + without calling constructors? */ +template Containers::Array initializerListToArrayWithDefaultDeleter(const std::initializer_list list) { + Containers::Array out{list.size()}; + /* FFS why initializer list doesn't have an operator[] */ + std::size_t i = 0; + for(auto&& item: list) out[i++] = item; + return out; +} + +}}} + +#endif diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp new file mode 100644 index 0000000000..948526f52d --- /dev/null +++ b/src/Magnum/Trade/MeshData.cpp @@ -0,0 +1,720 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "MeshData.h" + +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/PackingBatch.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" + +namespace Magnum { namespace Trade { + +MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: _type{type}, _data{data} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ + CORRADE_ASSERT(data.size()%meshIndexTypeSize(type) == 0, + "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); +} + +MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& data) noexcept { + if(data.size()[1] == 4) _type = MeshIndexType::UnsignedInt; + else if(data.size()[1] == 2) _type = MeshIndexType::UnsignedShort; + else if(data.size()[1] == 1) _type = MeshIndexType::UnsignedByte; + else CORRADE_ASSERT(false, "Trade::MeshIndexData: expected index type size 1, 2 or 4 but got" << data.size()[1], ); + + CORRADE_ASSERT(data.isContiguous(), "Trade::MeshIndexData: view is not contiguous", ); + _data = data.asContiguous(); +} + +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, arraySize, data, nullptr} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ + /** @todo support zero / negative stride? would be hard to transfer to GL */ + CORRADE_ASSERT(data.empty() || isVertexFormatImplementationSpecific(format) || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), + "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), ); +} + +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, arraySize, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ + #ifndef CORRADE_NO_ASSERT + if(arraySize) CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format)*arraySize, + "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format << "and array size" << arraySize, ); + else CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format), + "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); + #endif + CORRADE_ASSERT(data.isContiguous<1>(), + "Trade::MeshAttributeData: second view dimension is not contiguous", ); +} + +Containers::Array meshAttributeDataNonOwningArray(const Containers::ArrayView view) { + /* Ugly, eh? */ + return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: _indexType{indices._type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{Containers::arrayCast(indices._data)} { + /* Save vertex count. If it's passed explicitly, use that (but still check + that all attributes have the same vertex count for safety), otherwise + expect at least one attribute */ + #ifndef CORRADE_NO_ASSERT + UnsignedInt expectedAttributeVertexCount; + #endif + if(_attributes.empty()) { + CORRADE_ASSERT(vertexCount != ImplicitVertexCount, + "Trade::MeshData: vertex count can't be implicit if there are no attributes", ); + _vertexCount = vertexCount; + /* No attributes, so we won't be checking anything */ + } else if(vertexCount != ImplicitVertexCount) { + _vertexCount = vertexCount; + #ifndef CORRADE_NO_ASSERT + expectedAttributeVertexCount = _attributes[0]._vertexCount; + #endif + } else { + _vertexCount = _attributes[0]._vertexCount; + #ifndef CORRADE_NO_ASSERT + expectedAttributeVertexCount = _vertexCount; + #endif + } + + CORRADE_ASSERT(!_indices.empty() || _indexData.empty(), + "Trade::MeshData: indexData passed for a non-indexed mesh", ); + CORRADE_ASSERT(!_indices || (_indices.begin() >= _indexData.begin() && _indices.end() <= _indexData.end()), + "Trade::MeshData: indices [" << Debug::nospace << static_cast(_indices.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indices.end()) << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indexData.end()) << Debug::nospace << "]", ); + + #ifndef CORRADE_NO_ASSERT + /* Not checking what's already checked in MeshIndexData / MeshAttributeData + constructors */ + for(std::size_t i = 0; i != _attributes.size(); ++i) { + const MeshAttributeData& attribute = _attributes[i]; + CORRADE_ASSERT(attribute._format != VertexFormat{}, + "Trade::MeshData: attribute" << i << "doesn't specify anything", ); + CORRADE_ASSERT(attribute._vertexCount == expectedAttributeVertexCount, + "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << expectedAttributeVertexCount << "expected", ); + /* Check that the view fits into the provided vertex data array. For + implementation-specific formats we don't know the size so use 0 to + check at least partially. */ + const UnsignedInt typeSize = + isVertexFormatImplementationSpecific(attribute._format) ? 0 : + vertexFormatSize(attribute._format); + if(attribute._isOffsetOnly) { + const std::size_t size = attribute._data.offset + (_vertexCount - 1)*attribute._stride + typeSize; + CORRADE_ASSERT(!_vertexCount || size <= _vertexData.size(), + "Trade::MeshData: offset attribute" << i << "spans" << size << "bytes but passed vertexData array has only" << _vertexData.size(), ); + } else { + const void* const begin = static_cast(attribute._data.pointer); + const void* const end = static_cast(attribute._data.pointer) + (_vertexCount - 1)*attribute._stride + typeSize; + CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + } + } + #endif +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _indexDataFlags = indexDataFlags; + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, std::move(vertexData), std::move(attributes), vertexCount, importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + _indexDataFlags = indexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(indexData), indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, vertexCount, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, vertexCount, importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + _indexDataFlags = indexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, {}, {}, vertexCount, importerState} {} + +MeshData::~MeshData() = default; + +MeshData::MeshData(MeshData&&) noexcept = default; + +MeshData& MeshData::operator=(MeshData&&) noexcept = default; + +Containers::ArrayView MeshData::mutableIndexData() & { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndexData(): index data not mutable", {}); + return _indexData; +} + +Containers::ArrayView MeshData::mutableVertexData() & { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableVertexData(): vertex data not mutable", {}); + return _vertexData; +} + +UnsignedInt MeshData::indexCount() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexCount(): the mesh is not indexed", {}); + return _indices.size()/meshIndexTypeSize(_indexType); +} + +MeshIndexType MeshData::indexType() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexType(): the mesh is not indexed", {}); + return _indexType; +} + +std::size_t MeshData::indexOffset() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexOffset(): the mesh is not indexed", {}); + return _indices.data() - _indexData.data(); +} + +Containers::StridedArrayView2D MeshData::indices() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indices(): the mesh is not indexed", {}); + const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); + /* Build a 2D view using information about attribute type size */ + return {_indices, {_indices.size()/indexTypeSize, indexTypeSize}}; +} + +Containers::StridedArrayView2D MeshData::mutableIndices() { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndices(): index data not mutable", {}); + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::mutableIndices(): the mesh is not indexed", {}); + const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); + /* Build a 2D view using information about attribute type size */ + Containers::StridedArrayView2D out{_indices, {_indices.size()/indexTypeSize, indexTypeSize}}; + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +MeshAttributeData MeshData::attributeData(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeData(): index" << id << "out of range for" << _attributes.size() << "attributes", MeshAttributeData{}); + const MeshAttributeData& attribute = _attributes[id]; + return MeshAttributeData{attribute._name, attribute._format, attributeDataViewInternal(attribute)}; +} + +MeshAttribute MeshData::attributeName(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._name; +} + +VertexFormat MeshData::attributeFormat(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._format; +} + +std::size_t MeshData::attributeOffset(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._isOffsetOnly ? _attributes[id]._data.offset : + static_cast(_attributes[id]._data.pointer) - _vertexData.data(); +} + +UnsignedInt MeshData::attributeStride(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._stride; +} + +UnsignedShort MeshData::attributeArraySize(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._arraySize; +} + +UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { + UnsignedInt count = 0; + for(const MeshAttributeData& attribute: _attributes) + if(attribute._name == name) ++count; + return count; +} + +UnsignedInt MeshData::attributeFor(const MeshAttribute name, UnsignedInt id) const { + for(std::size_t i = 0; i != _attributes.size(); ++i) { + if(_attributes[i]._name != name) continue; + if(id-- == 0) return i; + } + + #ifdef CORRADE_NO_ASSERT + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + #else + return ~UnsignedInt{}; + #endif +} + +UnsignedInt MeshData::attributeId(const MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeId(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeId; +} + +VertexFormat MeshData::attributeFormat(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeFormat(attributeId); +} + +std::size_t MeshData::attributeOffset(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeOffset(attributeId); +} + +UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeStride(attributeId); +} + +UnsignedShort MeshData::attributeArraySize(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeArraySize(attributeId); +} + +Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset : + attribute._data.pointer, ~std::size_t{}}, + /* Not using attribute._vertexCount because that gets stale after + releaseVertexData() gets called, and then we would need to slice the + result inside attribute() and elsewhere anyway */ + _vertexCount, attribute._stride}; +} + +Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + const MeshAttributeData& attribute = _attributes[id]; + /* Build a 2D view using information about attribute type size */ + return Containers::arrayCast<2, const char>( + attributeDataViewInternal(attribute), + isVertexFormatImplementationSpecific(attribute._format) ? + attribute._stride : vertexFormatSize(attribute._format)* + (attribute._arraySize ? attribute._arraySize : 1)); +} + +Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + const MeshAttributeData& attribute = _attributes[id]; + /* Build a 2D view using information about attribute type size */ + auto out = Containers::arrayCast<2, const char>( + attributeDataViewInternal(attribute), + isVertexFormatImplementationSpecific(attribute._format) ? + attribute._stride : vertexFormatSize(attribute._format)* + (attribute._arraySize ? attribute._arraySize : 1)); + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +Containers::StridedArrayView2D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attribute(attributeId); +} + +Containers::StridedArrayView2D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return mutableAttribute(attributeId); +} + +namespace { + +template void convertIndices(const Containers::ArrayView data, const Containers::ArrayView destination) { + const auto input = Containers::arrayCast(data); + for(std::size_t i = 0; i != input.size(); ++i) destination[i] = input[i]; +} + +} + +void MeshData::indicesInto(const Containers::ArrayView destination) const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indicesInto(): the mesh is not indexed", ); + CORRADE_ASSERT(destination.size() == indexCount(), "Trade::MeshData::indicesInto(): expected a view with" << indexCount() << "elements but got" << destination.size(), ); + + switch(_indexType) { + case MeshIndexType::UnsignedByte: return convertIndices(_indices, destination); + case MeshIndexType::UnsignedShort: return convertIndices(_indices, destination); + case MeshIndexType::UnsignedInt: return convertIndices(_indices, destination); + } + + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::indicesAsArray() const { + /* Repeating the assert here because otherwise it would fire in + indexCount() which may be confusing */ + CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indicesAsArray(): the mesh is not indexed", {}); + Containers::Array output{indexCount()}; + indicesInto(output); + return output; +} + +void MeshData::positions2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const auto destination2f = Containers::arrayCast<2, Float>(destination); + + /* Copy 2D positions as-is, for 3D positions ignore Z */ + if(attribute._format == VertexFormat::Vector2 || + attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attributeData), destination); + else if(attribute._format == VertexFormat::Vector2h || + attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ub || + attribute._format == VertexFormat::Vector3ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2b || + attribute._format == VertexFormat::Vector3b) + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2us || + attribute._format == VertexFormat::Vector3us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2s || + attribute._format == VertexFormat::Vector3s) + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ubNormalized || + attribute._format == VertexFormat::Vector3ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2bNormalized || + attribute._format == VertexFormat::Vector3bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2usNormalized || + attribute._format == VertexFormat::Vector3usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2sNormalized || + attribute._format == VertexFormat::Vector3sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::positions2DAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + positions2DInto(out, id); + return out; +} + +void MeshData::positions3DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const Containers::StridedArrayView2D destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); + const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(destination); + + /* For 2D positions copy the XY part to the first two components */ + if(attribute._format == VertexFormat::Vector2) + Utility::copy(Containers::arrayCast(attributeData), + Containers::arrayCast(destination)); + else if(attribute._format == VertexFormat::Vector2h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2b) + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2s) + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); + + /* Copy 3D positions as-is */ + else if(attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attributeData), destination); + else if(attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3b) + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3s) + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* For 2D positions finally fill the Z with a single value */ + if(attribute._format == VertexFormat::Vector2 || + attribute._format == VertexFormat::Vector2h || + attribute._format == VertexFormat::Vector2ub || + attribute._format == VertexFormat::Vector2b || + attribute._format == VertexFormat::Vector2us || + attribute._format == VertexFormat::Vector2s || + attribute._format == VertexFormat::Vector2ubNormalized || + attribute._format == VertexFormat::Vector2bNormalized || + attribute._format == VertexFormat::Vector2usNormalized || + attribute._format == VertexFormat::Vector2sNormalized) { + constexpr Float z[1]{0.0f}; + Utility::copy( + Containers::stridedArrayView(z).broadcasted<0>(_vertexCount), + destination3f.transposed<0, 1>()[2]); + } +} + +Containers::Array MeshData::positions3DAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + positions3DInto(out, id); + return out; +} + +void MeshData::normalsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Normal, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const auto destination3f = Containers::arrayCast<2, Float>(destination); + + if(attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attributeData), destination); + else if(attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::normalsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + normalsInto(out, id); + return out; +} + +void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::TextureCoordinates, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates) << "texture coordinate attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::textureCoordinates2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::textureCoordinatesInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const auto destination2f = Containers::arrayCast<2, Float>(destination); + + if(attribute._format == VertexFormat::Vector2) + Utility::copy(Containers::arrayCast(attributeData), destination); + else if(attribute._format == VertexFormat::Vector2h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2b) + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2s) + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::textureCoordinates2DAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + textureCoordinates2DInto(out, id); + return out; +} + +void MeshData::colorsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Color, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); + const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); + + /* For three-component colors copy the RGB part to the first three + components */ + if(attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attributeData), + Containers::arrayCast(destination)); + else if(attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); + + /* Copy four-component colors as-is */ + else if(attribute._format == VertexFormat::Vector4) + Utility::copy(Containers::arrayCast(attributeData), + Containers::arrayCast(destination)); + else if(attribute._format == VertexFormat::Vector4h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); + else if(attribute._format == VertexFormat::Vector4ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4f); + else if(attribute._format == VertexFormat::Vector4usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* For three-component colors finally fill the alpha with a single value */ + if(attribute._format == VertexFormat::Vector3 || + attribute._format == VertexFormat::Vector3h || + attribute._format == VertexFormat::Vector3ubNormalized || + attribute._format == VertexFormat::Vector3usNormalized) { + constexpr Float alpha[1]{1.0f}; + Utility::copy( + Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount), + destination4f.transposed<0, 1>()[3]); + } +} + +Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + colorsInto(out, id); + return out; +} + +Containers::Array MeshData::releaseIndexData() { + _indices = {_indices.data(), 0}; + Containers::Array out = std::move(_indexData); + _indexData = Containers::Array{out.data(), 0, Implementation::nonOwnedArrayDeleter}; + return out; +} + +Containers::Array MeshData::releaseAttributeData() { + return std::move(_attributes); +} + +Containers::Array MeshData::releaseVertexData() { + _vertexCount = 0; + Containers::Array out = std::move(_vertexData); + _vertexData = Containers::Array{out.data(), 0, Implementation::nonOwnedArrayDeleter}; + return out; +} + +Debug& operator<<(Debug& debug, const MeshAttribute value) { + debug << "Trade::MeshAttribute" << Debug::nospace; + + if(UnsignedShort(value) >= UnsignedShort(MeshAttribute::Custom)) + return debug << "::Custom(" << Debug::nospace << (UnsignedShort(value) - UnsignedShort(MeshAttribute::Custom)) << Debug::nospace << ")"; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MeshAttribute::value: return debug << "::" << Debug::nospace << #value; + _c(Position) + _c(Normal) + _c(TextureCoordinates) + _c(Color) + #undef _c + /* LCOV_EXCL_STOP */ + + /* To silence compiler warning about unhandled values */ + case MeshAttribute::Custom: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h new file mode 100644 index 0000000000..1c4d60d079 --- /dev/null +++ b/src/Magnum/Trade/MeshData.h @@ -0,0 +1,1963 @@ +#ifndef Magnum_Trade_MeshData_h +#define Magnum_Trade_MeshData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::MeshData, @ref Magnum::Trade::MeshIndexData, @ref Magnum::Trade::MeshAttributeData, enum @ref Magnum::Trade::MeshAttribute, function @ref Magnum::Trade::isMeshAttributeCustom(), @ref Magnum::Trade::meshAttributeCustom() + * @m_since_latest + */ + +#include +#include + +#include "Magnum/Mesh.h" +#include "Magnum/VertexFormat.h" +#include "Magnum/Trade/Data.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace Trade { + +/** +@brief Mesh attribute name +@m_since_latest + +@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat, + @ref AbstractImporter::meshAttributeForName(), + @ref AbstractImporter::meshAttributeName() +*/ +/* 16 bits because 8 bits is not enough to cover all potential per-edge, + per-face, per-instance and per-meshlet attributes */ +enum class MeshAttribute: UnsignedShort { + /* 0 reserved for an invalid value (returned from + AbstractImporter::meshAttributeForName()) */ + + /** + * Position. Type is usually @ref VertexFormat::Vector2 for 2D and + * @ref VertexFormat::Vector3 for 3D, but can be also any of + * @ref VertexFormat::Vector2h, @ref VertexFormat::Vector3h, + * @ref VertexFormat::Vector2ub, @ref VertexFormat::Vector2ubNormalized, + * @ref VertexFormat::Vector2b, @ref VertexFormat::Vector2bNormalized, + * @ref VertexFormat::Vector2us, @ref VertexFormat::Vector2usNormalized, + * @ref VertexFormat::Vector2s, @ref VertexFormat::Vector2sNormalized, + * @ref VertexFormat::Vector3ub, @ref VertexFormat::Vector3ubNormalized, + * @ref VertexFormat::Vector3b, @ref VertexFormat::Vector3bNormalized, + * @ref VertexFormat::Vector3us, @ref VertexFormat::Vector3usNormalized, + * @ref VertexFormat::Vector3s or @ref VertexFormat::Vector3sNormalized. + * Corresponds to @ref Shaders::Generic::Position. + * @see @ref MeshData::positions2DAsArray(), + * @ref MeshData::positions3DAsArray() + */ + Position = 1, + + /** + * Normal. Type is usually @ref VertexFormat::Vector3, but can be also + * @ref VertexFormat::Vector3h. @ref VertexFormat::Vector3bNormalized or + * @ref VertexFormat::Vector3sNormalized. Corresponds to + * @ref Shaders::Generic::Normal. + * @see @ref MeshData::normalsAsArray() + */ + Normal, + + /** + * Texture coordinates. Type is usually @ref VertexFormat::Vector2 for + * 2D coordinates, but can be also any of @ref VertexFormat::Vector2h, + * @ref VertexFormat::Vector2ub, @ref VertexFormat::Vector2ubNormalized, + * @ref VertexFormat::Vector2b, @ref VertexFormat::Vector2bNormalized, + * @ref VertexFormat::Vector2us, @ref VertexFormat::Vector2usNormalized, + * @ref VertexFormat::Vector2s or @ref VertexFormat::Vector2sNormalized. + * Corresponds to @ref Shaders::Generic::TextureCoordinates. + * @see @ref MeshData::textureCoordinates2DAsArray() + */ + TextureCoordinates, + + /** + * Vertex color. Type is usually @ref VertexFormat::Vector3 or + * @ref VertexFormat::Vector4, but can be also any of + * @ref VertexFormat::Vector3h, @ref VertexFormat::Vector4h, + * @ref VertexFormat::Vector3ubNormalized, + * @ref VertexFormat::Vector3usNormalized, + * @ref VertexFormat::Vector4ubNormalized or + * @ref VertexFormat::Vector4usNormalized. Corresponds to + * @ref Shaders::Generic::Color3 or @ref Shaders::Generic::Color4. + * @see @ref MeshData::colorsAsArray() + */ + Color, + + /** + * This and all higher values are for importer-specific attributes. Can be + * of any type. See documentation of a particular importer for details. + * @see @ref isMeshAttributeCustom(MeshAttribute) + * @ref meshAttributeCustom(MeshAttribute), + * @ref meshAttributeCustom(UnsignedShort) + */ + Custom = 32768 +}; + +/** +@debugoperatorenum{MeshAttribute} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MeshAttribute value); + +/** +@brief Whether a mesh attribute is custom +@m_since_latest + +Returns @cpp true @ce if @p name has a value larger or equal to +@ref MeshAttribute::Custom, @cpp false @ce otherwise. +@see @ref meshAttributeCustom(UnsignedShort), + @ref meshAttributeCustom(MeshAttribute) +*/ +constexpr bool isMeshAttributeCustom(MeshAttribute name) { + return UnsignedShort(name) >= UnsignedShort(MeshAttribute::Custom); +} + +/** +@brief Create a custom mesh attribute +@m_since_latest + +Returns a custom mesh attribute with index @p id. The index is expected to be +less than the value of @ref MeshAttribute::Custom. Use +@ref meshAttributeCustom(MeshAttribute) to get the index back. +*/ +/* Constexpr so it's usable for creating compile-time MeshAttributeData + instances */ +constexpr MeshAttribute meshAttributeCustom(UnsignedShort id) { + return CORRADE_CONSTEXPR_ASSERT(id < UnsignedShort(MeshAttribute::Custom), + "Trade::meshAttributeCustom(): index" << id << "too large"), + MeshAttribute(UnsignedShort(MeshAttribute::Custom) + id); +} + +/** +@brief Get index of a custom mesh attribute +@m_since_latest + +Inverse to @ref meshAttributeCustom(UnsignedShort). Expects that the attribute +is custom. +@see @ref isMeshAttributeCustom() +*/ +constexpr UnsignedShort meshAttributeCustom(MeshAttribute name) { + return CORRADE_CONSTEXPR_ASSERT(isMeshAttributeCustom(name), + "Trade::meshAttributeCustom():" << name << "is not custom"), + UnsignedShort(name) - UnsignedShort(MeshAttribute::Custom); +} + +/** +@brief Mesh index data +@m_since_latest + +Convenience type for populating @ref MeshData, see its documentation for an +introduction. + +@section Trade-MeshIndexData-usage Usage + +The most straightforward usage is constructing the instance from a view on the +index array. The @ref MeshIndexType gets inferred from the view type: + +@snippet MagnumTrade.cpp MeshIndexData-usage + +Alternatively, you can pass a typeless @cpp const void @ce view and supply +@ref MeshIndexType explicitly, or a contiguous 2D view and let the class detect +the actual index type from second dimension size. Note that the class accepts +only contiguous views and not @ref Corrade::Containers::StridedArrayView, +following limitations of GPU index buffers that also have to be contiguous. +@see @ref MeshAttributeData +*/ +class MAGNUM_TRADE_EXPORT MeshIndexData { + public: + /** @brief Construct for a non-indexed mesh */ + explicit MeshIndexData(std::nullptr_t = nullptr) noexcept: _type{} {} + + /** + * @brief Construct with a runtime-specified index type + * @param type Index type + * @param data Index data + * + * The @p data size is expected to correspond to given @p type (e.g., + * for @ref MeshIndexType::UnsignedInt the @p data array size should + * be divisible by 4). If you know the @p type at compile time, you can + * use one of the @ref MeshIndexData(Containers::ArrayView), + * @ref MeshIndexData(Containers::ArrayView) or + * @ref MeshIndexData(Containers::ArrayView) + * constructors, which infer the index type automatically. + * + * If @p data is empty, the mesh will be treated as indexed but with + * zero indices. To create a non-indexed mesh, use the + * @ref MeshIndexData(std::nullptr_t) constructor. + */ + explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data) noexcept; + + /** @brief Construct with unsigned byte indices */ + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedByte}, _data{data} {} + + /** @brief Construct with unsigned short indices */ + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedShort}, _data{data} {} + + /** @brief Construct with unsigned int indices */ + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedInt}, _data{data} {} + + /** + * @brief Constructor + * + * Expects that @p data is contiguous and size of the second dimension + * is either 1, 2 or 4, corresponding to one of the @ref MeshIndexType + * values. + */ + explicit MeshIndexData(const Containers::StridedArrayView2D& data) noexcept; + + /** @brief Index type */ + constexpr MeshIndexType type() const { return _type; } + + /** @brief Type-erased index data */ + constexpr Containers::ArrayView data() const { return _data; } + + private: + friend MeshData; + MeshIndexType _type; + /* Void so the constructors can be constexpr */ + Containers::ArrayView _data; +}; + +/** +@brief Mesh attribute data +@m_since_latest + +Convenience type for populating @ref MeshData, see its documentation for an +introduction. Additionally usable in various @ref MeshTools algorithms such as +@ref MeshTools::duplicate(const Trade::MeshData& data, Containers::ArrayView) +or @ref MeshTools::interleave(const Trade::MeshData& data, Containers::ArrayView). + +@section Trade-MeshAttributeData-usage Usage + +The most straightforward usage is constructing an instance from a pair of +@ref MeshAttribute and a strided view. The @ref VertexFormat gets inferred from +the view type: + +@snippet MagnumTrade.cpp MeshAttributeData-usage + +Alternatively, you can pass a typeless @cpp const void @ce view and supply +@ref VertexFormat explicitly, or a 2D view. + +@subsection Trade-MeshAttributeData-usage-offset-only Offset-only attribute data + +If the actual attribute data location is not known yet, the instance can be +created as "offset-only", meaning the actual view gets created only later when +passed to a @ref MeshData instance with a concrete vertex data array. This is +useful for example when vertex layout is static (and thus can be defined at +compile time), but the actual data is allocated / populated at runtime: + +@snippet MagnumTrade.cpp MeshAttributeData-usage-offset-only + +Note that @ref MeshTools algorithms generally don't accept offset-only +@ref MeshAttributeData instances except when passed through a @ref MeshData +instance. + +@section Trade-MeshAttributeData-custom-vertex-format Custom vertex formats + +Apart from custom @ref MeshAttribute names, shown in +@ref Trade-MeshData-populating-custom, @ref VertexFormat can be extended with +implementation-specific formats as well. Formats that don't have a generic +@ref VertexFormat equivalent can be created using @ref vertexFormatWrap(), +however note that most APIs and @ref MeshTools functions can't work with those +as their size or contents can't be known: + +@snippet MagnumTrade.cpp MeshAttributeData-custom-vertex-format + +@see @ref MeshIndexData +*/ +class MAGNUM_TRADE_EXPORT MeshAttributeData { + public: + /** + * @brief Default constructor + * + * Leaves contents at unspecified values. Provided as a convenience for + * initialization of the attribute array for @ref MeshData, expected to + * be replaced with concrete values later. + */ + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _arraySize{}, _isOffsetOnly{false} {} + + /** + * @brief Type-erased constructor + * @param name Attribute name + * @param format Vertex format + * @param data Attribute data + * + * Expects that @p data stride is large enough to fit @p type and that + * @p type corresponds to @p name. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, 0, data} {} + + /** + * @brief Type-erased constructor for an array attribute + * @param name Attribute name + * @param format Vertex format + * @param arraySize Array size + * @param data Attribute data + * + * Expects that @p data stride is large enough to fit @p type, @p type + * corresponds to @p name and @p arraySize is zero for builtin + * attributes. Passing @cpp 0 @ce to @p arraySize is equivalent to + * calling the above overload. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data) noexcept; + + /** + * @brief Constructor + * @param name Attribute name + * @param format Vertex format + * @param data Attribute data + * + * Expects that the second dimension of @p data is contiguous and its + * size matches @p type; and that @p type corresponds to @p name. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, 0, data} {} + + /** @overload */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t) noexcept: MeshAttributeData{name, format, 0, nullptr, nullptr} {} + + /** + * @brief Construct an array attribute + * @param name Attribute name + * @param format Vertex format + * @param arraySize Array size + * @param data Attribute data + * + * Expects that the second dimension of @p data is contiguous and its + * size matches @p type and @p arraSize, that @p type corresponds to + * @p name and @p arraySize is zero for builtin attributes. Passing + * @cpp 0 @ce to @p arraySize is equivalent to calling the above + * overload. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView2D& data) noexcept; + + /** + * @brief Constructor + * @param name Attribute name + * @param data Attribute data + * + * Detects @ref VertexFormat based on @p T and calls + * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&). + * For most types known by Magnum, the detected @ref VertexFormat is of + * the same name as the type (so e.g. @ref Magnum::Vector3ui "Vector3ui" + * gets recognized as @ref VertexFormat::Vector3ui), with the + * following exceptions: + * + * - @ref Color3ub is recognized as + * @ref VertexFormat::Vector3ubNormalized + * - @ref Color3us is recognized as + * @ref VertexFormat::Vector3usNormalized + * - @ref Color4ub is recognized as + * @ref VertexFormat::Vector4ubNormalized + * - @ref Color4us is recognized as + * @ref VertexFormat::Vector4usNormalized + * + * This also means that if you have a @ref Magnum::Vector2s "Vector2s", + * for example, and want to pick a + * @ref VertexFormat::Vector2sNormalized instead of the + * (autodetected) @ref VertexFormat::Vector2s, you need to specify + * it explicitly --- there's no way the library can infer this from the + * type alone, except for the color types above (which are generally + * always normalized). + */ + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept; + + /** @overload */ + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + + /** + * @brief Construct an array attribute + * @param name Attribute name + * @param data Attribute data + * + * Detects @ref VertexFormat based on @p T and calls + * @ref MeshAttributeData(MeshAttribute, VertexFormat, UnsignedShort, const Containers::StridedArrayView1D&) + * with the second dimension size passed to @p arraySize. Expects that + * the second dimension is contiguous. At the moment only custom + * attributes can be arrays, which means this function can't be used + * with a builtin @p name. See @ref MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + * for details about @ref VertexFormat detection. + */ + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept; + + /** + * @brief Construct an offset-only attribute + * @param name Attribute name + * @param format Attribute format + * @param offset Attribute data offset + * @param vertexCount Attribute vertex count + * @param stride Attribute stride + * @param arraySize Array size. Use @cpp 0 @ce for non-array + * attributes. + * + * Instances created this way refer to an offset in unspecified + * external vertex data instead of containing the data view directly. + * Useful when the location of the vertex data array is not known at + * attribute construction time. Expects that @p arraySize is zero for + * builtin attributes. Note that instances created this way can't be + * used in most @ref MeshTools algorithms. + * + * Additionally, for even more flexibility, the @p vertexCount can be + * overriden at @ref MeshData construction time, however all attributes + * are still required to have the same vertex count to catch accidents. + * @see @ref isOffsetOnly(), @ref arraySize(), + * @ref data(Containers::ArrayView) const + */ + explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride, UnsignedShort arraySize = 0) noexcept; + + /** + * @brief Construct a pad value + * + * Usable in various @ref MeshTools algorithms to insert padding + * between interleaved attributes. Negative values can be used to alias + * multiple different attributes onto each other. Not meant to be + * passed to @ref MeshData. + * @see @ref stride() + */ + constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{ + (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, + "Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding)) + }, _name{}, _arraySize{}, _isOffsetOnly{false} {} + + /** + * @brief If the attribute is offset-only + * + * Returns @cpp true @ce if the attribute doesn't contain the data view + * directly, but instead refers to unspecified external vertex data. + * @see @ref data(Containers::ArrayView) const, + * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort) + */ + constexpr bool isOffsetOnly() const { return _isOffsetOnly; } + + /** @brief Attribute name */ + constexpr MeshAttribute name() const { return _name; } + + /** @brief Attribute format */ + constexpr VertexFormat format() const { return _format; } + + /** + * @brief Attribute offset + * + * If the attribute is offset-only, returns the offset directly, + * otherwise uses the @p vertexData parameter to calculate the offset. + * @see @ref isOffsetOnly() + */ + std::size_t offset(Containers::ArrayView vertexData) const { + return _isOffsetOnly ? _data.offset : reinterpret_cast(_data.pointer) - reinterpret_cast(vertexData.data()); + } + + /** + * @brief Attribute stride + * + * Can be negative for pad values, never negative for real attributes. + * @see @ref MeshAttributeData(Int) + */ + constexpr Short stride() const { return _stride; } + + /** @brief Attribute array size */ + constexpr UnsignedShort arraySize() const { return _arraySize; } + + /** + * @brief Type-erased attribute data + * + * Expects that the attribute is not offset-only, in that case use the + * @ref data(Containers::ArrayView) const overload + * instead. + * @see @ref isOffsetOnly() + */ + constexpr Containers::StridedArrayView1D data() const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {_data.pointer, ~std::size_t{}}, _vertexCount, + (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array"), _stride)}; + } + + /** + * @brief Type-erased attribute data for an offset-only attribute + * + * If the attribute is not offset-only, the @p vertexData parameter is + * ignored. + * @see @ref isOffsetOnly(), @ref data() const + */ + Containers::StridedArrayView1D data(Containers::ArrayView vertexData) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + vertexData, _isOffsetOnly ? reinterpret_cast(vertexData.data()) + _data.offset : _data.pointer, _vertexCount, _stride}; + } + + private: + constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; + + friend MeshData; + union Data { + /* FFS C++ why this doesn't JUST WORK goddamit?! */ + constexpr Data(const void* pointer = nullptr): pointer{pointer} {} + constexpr Data(std::size_t offset): offset{offset} {} + + const void* pointer; + std::size_t offset; + } _data; + /* Vertex count in MeshData is currently 32-bit, so this doesn't need + to be 64-bit either */ + UnsignedInt _vertexCount; + VertexFormat _format; + /* According to https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_VERTEX_ATTRIB_STRIDE, + current largest reported stride is 4k so 32k should be enough */ + Short _stride; + MeshAttribute _name; + UnsignedShort _arraySize; + bool _isOffsetOnly; + /* 1 byte free for more stuff on 64b (23, aligned to 24) and on 32b + (19, aligned to 20) */ +}; + +/** @relatesalso MeshAttributeData +@brief Create a non-owning array of @ref MeshAttributeData items +@m_since_latest + +Useful when you have the attribute definitions statically defined (for example +when the vertex data themselves are already defined at compile time) and don't +want to allocate just to pass those to @ref MeshData. +*/ +Containers::Array MAGNUM_TRADE_EXPORT meshAttributeDataNonOwningArray(Containers::ArrayView view); + +/** +@brief Mesh data +@m_since_latest + +Provides access to mesh vertex and index data, together with additional +information such as primitive type. Populated instances of this class are +returned from @ref AbstractImporter::mesh() and from particular functions in +the @ref Primitives library. + +@section Trade-MeshData-usage Basic usage + +The simplest usage is through the convenience functions @ref positions2DAsArray(), +@ref positions3DAsArray(), @ref normalsAsArray(), @ref textureCoordinates2DAsArray() +and @ref colorsAsArray(). Each of these takes an index (as there can be +multiple sets of texture coordinates, for example) and you're expected to check +for attribute presence first with either @ref hasAttribute() or +@ref attributeCount(MeshAttribute) const: + +@snippet MagnumTrade.cpp MeshData-usage + +@section Trade-MeshData-usage-advanced Advanced usage + +The @ref positions2DAsArray(), ... functions shown above always return a +newly-allocated @ref Corrade::Containers::Array instance with a clearly defined +type that's large enough to represent most data. While that's fine for many use +cases, sometimes you may want to minimize the import time of a large model or +the imported data may be already in a well-optimized layout and format that you +want to preserve. The @ref MeshData class internally stores a contiguous blob +of data, which you can directly upload, and then use provided metadata to let +the GPU know of the format and layout. Because there's a lot of possible types +of each attribute (floats, packed integers, ...), the @ref GL::DynamicAttribute +can accept a pair of @ref GL::Attribute defined by the shader and the actual +@ref VertexFormat, figuring out all properties such as component count and +element data type without having to explicitly handle all relevant types: + +@snippet MagnumTrade.cpp MeshData-usage-advanced + +@section Trade-MeshData-usage-compile Using MeshTools::compile() + +For a quick yet efficient way to upload all data and configure a mesh for all +known attributes that are present, @ref MeshTools::compile() can be used. +Compared to the above, it's just an oneliner: + +@snippet MagnumTrade.cpp MeshData-usage-compile + +Compared to configuring the mesh manually you may lose a bit of flexibility, +especially when you need to set up custom attributes or modify the data after. +See @ref MeshTools::compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) +for a possible solution. + +@section Trade-MeshData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained index +and vertex data through the @ref indexData(), @ref vertexData(), +@ref indices() and @ref attribute() accessors. This is done because in general +case the data can also refer to a memory-mapped file or constant memory. In +cases when it's desirable to modify the data in-place, there's the +@ref mutableIndexData(), @ref mutableVertexData(), @ref mutableIndices() and +@ref mutableAttribute() set of functions. To use these, you need to check that +the data are mutable using @ref indexDataFlags() or @ref vertexDataFlags() +first. The following snippet applies a transformation to the mesh data: + +@snippet MagnumTrade.cpp MeshData-usage-mutable + +@section Trade-MeshData-populating Populating an instance + +A @ref MeshData instance by default takes over the ownership of an +@ref Corrade::Containers::Array containing the vertex / index data together +with a @ref MeshIndexData instance and a list of @ref MeshAttributeData +describing various index and vertex properties. For example, an interleaved +indexed mesh with 3D positions and RGBA colors would look like this --- +and variants with just vertex data or just index data or neither are possible +too: + +@snippet MagnumTrade.cpp MeshData-populating + +In cases where you want the @ref MeshData instance to only refer to external +data without taking ownership (for example in a memory-mapped file, constant +memory etc., instead of moving in an @ref Corrade::Containers::Array you pass +@ref DataFlags describing if the data is mutable or not together with an +@ref Corrade::Containers::ArrayView. A variant of the above where the index +data is constant and vertex data mutable, both referenced externally: + +@snippet MagnumTrade.cpp MeshData-populating-non-owned + +@subsection Trade-MeshData-populating-custom Custom mesh attributes + +To allow for greater flexibility, a @ref MeshData instance can describe not +just attributes that are predefined in the @ref MeshAttribute enum, but also +custom attributes, created with @ref meshAttributeCustom(). For example, the +snippet below describes a custom per-face structure that exposes faces as +higher-order polygons combining multiple triangles together ---in this case, +each face has an array of 15 IDs, which is exposed as a 2D array: + +@snippet MagnumTrade.cpp MeshData-populating-custom + +Later, the (array) attributes can be retrieved back using the same custom +identifiers --- note the use of @cpp [] @ce to get back a 2D array again: + +@snippet MagnumTrade.cpp MeshData-populating-custom-retrieve + +When a custom attribute is exposed through @ref AbstractImporter, it's possible +to map custom @ref MeshAttribute values to human-readable string names using +@ref AbstractImporter::meshAttributeName() and +@ref AbstractImporter::meshAttributeForName(). Using @ref meshPrimitiveWrap() +you can also supply implementation-specific values that are not available in +the generic @ref MeshPrimitive enum, similarly see also +@ref Trade-MeshAttributeData-custom-vertex-format for details on +implementation-specific @ref VertexFormat values. +@see @ref AbstractImporter::mesh() +*/ +class MAGNUM_TRADE_EXPORT MeshData { + public: + enum: UnsignedInt { + /** + * Implicit vertex count. When passed to a constructor, indicates + * that vertex count should be taken from attribute data views. + */ + ImplicitVertexCount = ~UnsignedInt{} + }; + + /** + * @brief Construct an indexed mesh data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). + * @param importerState Importer-specific state + * + * The @p indices are expected to point to a sub-range of @p indexData. + * The @p attributes are expected to reference (sparse) sub-ranges of + * @p vertexData. If the mesh has no attributes, the @p indices are + * expected to be valid (but can be empty). If you want to create an + * attribute-less non-indexed mesh, use + * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify + * desired vertex count. + * + * The @ref indexDataFlags() / @ref vertexDataFlags() are implicitly + * set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, DataFlags, Containers::ArrayView, Containers::Array&&, UnsignedInt, const void*) + * constructor or its variants instead. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned index and vertex data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) + * creates an instance that doesn't own the passed vertex and index + * data. The @p indexDataFlags / @p vertexDataFlags parameters can + * contain @ref DataFlag::Mutable to indicate the external data can be + * modified, and is expected to *not* have @ref DataFlag::Owned set. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned index data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) + * creates an instance that doesn't own the passed index data. The + * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. The @ref vertexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned vertex data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) + * creates an instance that doesn't own the passed vertex data. The + * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. The @ref indexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); + + /** + * @brief Construct a non-indexed mesh data + * @param primitive Primitive + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). + * @param importerState Importer-specific state + * + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) + * with default-constructed @p indexData and @p indices arguments. + * + * The @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For consistency, + * the @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, Containers::Array&&, UnsignedInt, const void*) + * constructor instead. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); + + /** + * @brief Construct a non-owned non-indexed mesh data + * @param primitive Primitive + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) + * creates an instance that doesn't own the passed data. The + * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. For consistency, the + * @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); + + /** + * @brief Construct an attribute-less indexed mesh data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexCount Vertex count. Passing @ref ImplicitVertexCount + * is not allowed in this overload. + * @param importerState Importer-specific state + * + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) + * with default-constructed @p vertexData and @p attributes arguments. + * The @p indices are expected to be valid (but can be empty). If you + * want to create an attribute-less non-indexed mesh, use + * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify + * desired vertex count. + * + * The @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For consistency, + * the @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, UnsignedInt, const void*) + * constructor instead. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned attribute-less indexed mesh data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexCount Vertex count. Passing + * @ref ImplicitVertexCount is not allowed in this overload. + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, UnsignedInt, const void*) + * creates an instance that doesn't own the passed data. The + * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. For consistency, the + * @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an index-less attribute-less mesh data + * @param primitive Primitive + * @param vertexCount Vertex count. Passing @ref ImplicitVertexCount + * is not allowed in this overload. + * @param importerState Importer-specific state + * + * Useful in case the drawing is fully driven by a shader. For + * consistency, the @ref indexDataFlags() / @ref vertexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable, even though there isn't any data to own or + * to mutate. + */ + explicit MeshData(MeshPrimitive primitive, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; + + ~MeshData(); + + /** @brief Copying is not allowed */ + MeshData(const MeshData&) = delete; + + /** @brief Move constructor */ + MeshData(MeshData&&) noexcept; + + /** @brief Copying is not allowed */ + MeshData& operator=(const MeshData&) = delete; + + /** @brief Move assignment */ + MeshData& operator=(MeshData&&) noexcept; + + /** + * @brief Index data flags + * + * @see @ref releaseIndexData(), @ref mutableIndexData(), + * @ref mutableIndices() + */ + DataFlags indexDataFlags() const { return _indexDataFlags; } + + /** + * @brief Vertex data flags + * + * @see @ref releaseVertexData(), @ref mutableVertexData(), + * @ref mutableAttribute() + */ + DataFlags vertexDataFlags() const { return _vertexDataFlags; } + + /** @brief Primitive */ + MeshPrimitive primitive() const { return _primitive; } + + /** + * @brief Raw index data + * + * Returns @cpp nullptr @ce if the mesh is not indexed. + * @see @ref isIndexed(), @ref indexCount(), @ref indexType(), + * @ref indices(), @ref mutableIndexData(), @ref releaseIndexData() + */ + Containers::ArrayView indexData() const & { return _indexData; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView indexData() const && = delete; + + /** + * @brief Mutable raw index data + * + * Like @ref indexData(), but returns a non-const view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + Containers::ArrayView mutableIndexData() &; + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView mutableIndexData() && = delete; + + /** + * @brief Raw attribute metadata + * + * Returns the raw data that are used as a base for all `attribute*()` + * accessors, or @cpp nullptr @ce if the mesh has no attributes. In + * most cases you don't want to access those directly, but rather use + * the @ref attribute(), @ref attributeName(), @ref attributeFormat(), + * @ref attributeOffset(), @ref attributeStride() etc. accessors. + * Compared to those and to @ref attributeData(UnsignedInt) const, the + * @ref MeshAttributeData instances returned by this function may have + * different data pointers, and some of them might be offset-only --- + * use this function only if you *really* know what are you doing. + * @see @ref MeshAttributeData::isOffsetOnly() + */ + Containers::ArrayView attributeData() const & { return _attributes; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView attributeData() && = delete; + + /** + * @brief Raw vertex data + * + * Contains data for all vertex attributes. Returns @cpp nullptr @ce if + * the mesh has no attributes. + * @see @ref attributeCount(), @ref attributeName(), + * @ref attributeFormat(), @ref attribute(), + * @ref mutableVertexData(), @ref releaseVertexData() + */ + Containers::ArrayView vertexData() const & { return _vertexData; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView vertexData() const && = delete; + + /** + * @brief Mutable raw vertex data + * + * Like @ref vertexData(), but returns a non-const view. Expects that + * the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::ArrayView mutableVertexData() &; + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView mutableVertexData() && = delete; + + /** @brief Whether the mesh is indexed */ + bool isIndexed() const { return _indexType != MeshIndexType{}; } + + /** + * @brief Index count + * + * Count of elements in the @ref indices() array. Expects that the + * mesh is indexed; returned value is always non-zero. See also + * @ref vertexCount() which returns count of elements in every + * @ref attribute() array, and @ref attributeCount() which returns + * count of different per-vertex attribute arrays. + * @see @ref isIndexed(), @ref indexType() + */ + UnsignedInt indexCount() const; + + /** + * @brief Index type + * + * Expects that the mesh is indexed. + * @see @ref isIndexed(), @ref attributeFormat() + */ + MeshIndexType indexType() const; + + /** + * @brief Index offset + * + * Byte offset of the first index from the beginning of the + * @ref indexData(), or a byte difference between pointers returned + * from @ref indexData() and @ref indices(). Expects that the mesh is + * indexed. + * @see @ref attributeOffset() + */ + std::size_t indexOffset() const; + + /** + * @brief Mesh indices + * + * The view is guaranteed to be contiguous and its second dimension + * represents the actual data type (its size is equal to type size). + * Use the templated overload below to get the indices in a concrete + * type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D indices() const; + + /** + * @brief Mutable mesh indices + * + * Like @ref indices() const, but returns a mutable view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + Containers::StridedArrayView2D mutableIndices(); + + /** + * @brief Mesh indices in a concrete type + * + * Expects that the mesh is indexed and that @p T corresponds to + * @ref indexType(). You can also use the non-templated + * @ref indicesAsArray() accessor to get indices converted to 32-bit, + * but note that such operation involves extra allocation and data + * conversion. + * @see @ref isIndexed(), @ref attribute(), @ref mutableIndices() + */ + template Containers::ArrayView indices() const; + + /** + * @brief Mutable mesh indices in a concrete type + * + * Like @ref indices() const, but returns a mutable view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + template Containers::ArrayView mutableIndices(); + + /** + * @brief Mesh vertex count + * + * Count of elements in every attribute array returned by + * @ref attribute() (or, in case of an attribute-less mesh, the + * desired vertex count). See also @ref indexCount() which returns + * count of elements in the @ref indices() array, and + * @ref attributeCount() which returns count of different per-vertex + * attribute arrays. + */ + UnsignedInt vertexCount() const { return _vertexCount; } + + /** + * @brief Attribute array count + * + * Count of different per-vertex attribute arrays, or @cpp 0 @ce for an + * attribute-less mesh. See also @ref indexCount() which returns count + * of elements in the @ref indices() array and @ref vertexCount() which + * returns count of elements in every @ref attribute() array. + * @see @ref attributeCount(MeshAttribute) const + */ + UnsignedInt attributeCount() const { return _attributes.size(); } + + /** + * @brief Raw attribute data + * + * Returns the raw data that are used as a base for all `attribute*()` + * accessors. In most cases you don't want to access those directly, + * but rather use the @ref attribute(), @ref attributeName(), + * @ref attributeFormat(), @ref attributeOffset(), + * @ref attributeStride() etc. accessors. + * + * Useful mainly for passing particular attributes unchanged directly + * to @ref MeshTools algorithms --- unlike with @ref attributeData() + * and @ref releaseAttributeData(), returned instances are guaranteed + * to always have an absolute data pointer (i.e., + * @ref MeshAttributeData::isOffsetOnly() always returning + * @cpp false @ce). The @p id is expected to be smaller than + * @ref attributeCount() const. + * @see @ref MeshTools::interleavedData() + */ + MeshAttributeData attributeData(UnsignedInt id) const; + + /** + * @brief Attribute name + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * @see @ref attributeFormat(), @ref isMeshAttributeCustom(), + * @ref AbstractImporter::meshAttributeForName(), + * @ref AbstractImporter::meshAttributeName() + */ + MeshAttribute attributeName(UnsignedInt id) const; + + /** + * @brief Attribute format + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * You can also use @ref attributeFormat(MeshAttribute, UnsignedInt) const + * to directly get a type of given named attribute. + * @see @ref attributeName(), @ref indexType() + */ + VertexFormat attributeFormat(UnsignedInt id) const; + + /** + * @brief Attribute offset + * + * Byte offset of the first element of given attribute from the + * beginning of the @ref vertexData() array, or a byte difference + * between pointers returned from @ref vertexData() and a particular + * @ref attribute(). The @p id is expected to be smaller than + * @ref attributeCount() const. You can also use + * @ref attributeOffset(MeshAttribute, UnsignedInt) const to + * directly get an offset of given named attribute. + * @see @ref indexOffset(), @ref MeshTools::isInterleaved() + */ + std::size_t attributeOffset(UnsignedInt id) const; + + /** + * @brief Attribute stride + * + * Stride between consecutive elements of given attribute in the + * @ref vertexData() array. The @p id is expected to be smaller + * than @ref attributeCount() const. You can also use + * @ref attributeStride(MeshAttribute, UnsignedInt) const to + * directly get a stride of given named attribute. + * @see @ref MeshTools::isInterleaved() + */ + UnsignedInt attributeStride(UnsignedInt id) const; + + /** + * @brief Attribute array size + * + * In case given attribute is an array (the equivalent of e.g. + * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. + * At the moment only custom attributes can be arrays, no builtin + * @ref MeshAttribute is an array attribute. You can also use + * @ref attributeArraySize(MeshAttribute, UnsignedInt) const to + * directly get array size of given named attribute. + * + * Note that this is different from vertex count, which is exposed + * through @ref vertexCount(), and is an orthogonal concept to having + * multiple attributes of the same name (for example two sets of + * texture coordinates), which is exposed through + * @ref attributeCount(MeshAttribute) const. See + * @ref Trade-MeshData-populating-custom for an example. + * @see @ref isMeshAttributeCustom() + */ + UnsignedShort attributeArraySize(UnsignedInt id) const; + + /** + * @brief Whether the mesh has given attribute + * + * @see @ref attributeCount(MeshAttribute) const + */ + bool hasAttribute(MeshAttribute name) const { + return attributeCount(name); + } + + /** + * @brief Count of given named attribute + * + * Unlike @ref attributeCount() const this returns count for given + * attribute name --- for example a mesh can have more than one set of + * texture coordinates. + * @see @ref hasAttribute() + */ + UnsignedInt attributeCount(MeshAttribute name) const; + + /** + * @brief Absolute ID of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. + */ + UnsignedInt attributeId(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Format of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. + * @see @ref attributeFormat(UnsignedInt) const + */ + VertexFormat attributeFormat(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Offset of a named attribute + * + * Byte offset of the first element of given named attribute from the + * beginning of the @ref vertexData() array. The @p id is expected to + * be smaller than @ref attributeCount(MeshAttribute) const. + * @see @ref attributeOffset(UnsignedInt) const + */ + std::size_t attributeOffset(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Stride of a named attribute + * + * Stride between consecutive elements of given named attribute in the + * @ref vertexData() array. The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. + * @see @ref attributeStride(UnsignedInt) const + */ + UnsignedInt attributeStride(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Array size of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. Note that this is + * different from vertex count, and is an orthogonal concept to having + * multiple attributes of the same name --- see + * @ref attributeArraySize(UnsignedInt) const for more information. + */ + UnsignedShort attributeArraySize(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Data for given attribute + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * The second dimension represents the actual data type (its size is + * equal to format size for known @ref VertexFormat values and to + * attribute stride for implementation-specific values) and is + * guaranteed to be contiguous. Use the templated overload below to get + * the attribute in a concrete type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::StridedArrayView2D attribute(UnsignedInt id) const; + + /** + * @brief Mutable data for given attribute + * + * Like @ref attribute(UnsignedInt) const, but returns a mutable view. + * Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::StridedArrayView2D mutableAttribute(UnsignedInt id); + + /** + * @brief Data for given attribute in a concrete type + * + * The @p id is expected to be smaller than @ref attributeCount() const + * and @p T is expected to correspond to + * @ref attributeFormat(UnsignedInt) const. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(UnsignedInt) const + * above. The attribute is also expected to not be an array, in that + * case you need to use the overload below by using @cpp T[] @ce + * instead of @cpp T @ce. You can also use the non-templated + * @ref positions2DAsArray(), @ref positions3DAsArray(), + * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and + * @ref colorsAsArray() accessors to get common attributes converted to + * usual types, but note that these operations involve extra allocation + * and data conversion. + * @see @ref attribute(MeshAttribute, UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref isVertexFormatImplementationSpecific(), + * @ref attributeArraySize() + */ + template::value>::type> Containers::StridedArrayView1D attribute(UnsignedInt id) const; + + /** + * @brief Data for given array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> attribute(UnsignedInt id) const; + + /** + * @brief Mutable data for given attribute in a concrete type + * + * Like @ref attribute(UnsignedInt) const, but returns a mutable view. + * Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + template::value>::type> Containers::StridedArrayView1D mutableAttribute(UnsignedInt id); + + /** + * @brief Mutable data for given array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(UnsignedInt id); + + /** + * @brief Data for given named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. The second dimension + * represents the actual data type (its size is equal to format size + * for known @ref VertexFormat values and to attribute stride for + * implementation-specific values) and is guaranteed to be contiguous. + * Use the templated overload below to get the attribute in a concrete + * type. + * @see @ref attribute(UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Mutable data for given named attribute + * + * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a + * mutable view. Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::StridedArrayView2D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + + /** + * @brief Data for given named attribute in a concrete type + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const and @p T is expected to + * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. + * Expects that the vertex format is *not* implementation-specific, in + * that case you can only access the attribute via the typeless + * @ref attribute(MeshAttribute, UnsignedInt) const above. You can also + * use the non-templated @ref positions2DAsArray(), + * @ref positions3DAsArray(), @ref normalsAsArray(), + * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() + * accessors to get common attributes converted to usual types, but + * note that these operations involve extra data conversion and an + * allocation. + * @see @ref attribute(UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref isVertexFormatImplementationSpecific() + */ + template::value>::type> Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Data for given named array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Mutable data for given named attribute in a concrete type + * + * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a + * mutable view. Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + template::value>::type> Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + + /** + * @brief Mutable data for given named array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + + /** + * @brief Indices as 32-bit integers + * + * Convenience alternative to the templated @ref indices(). Converts + * the index array from an arbitrary underlying type and returns it in + * a newly-allocated array. + * @see @ref indicesInto() + */ + Containers::Array indicesAsArray() const; + + /** + * @brief Positions as 32-bit integers into a pre-allocated view + * + * Like @ref indicesAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref indexCount() + */ + void indicesInto(Containers::ArrayView destination) const; + + /** + * @brief Positions as 2D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Position as the first argument. Converts + * the position array from an arbitrary underlying type and returns it + * in a newly-allocated array. If the underlying type is + * three-component, the last component is dropped. Expects that the + * vertex format is *not* implementation-specific, in that case you can + * only access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref positions2DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array positions2DAsArray(UnsignedInt id = 0) const; + + /** + * @brief Positions as 2D float vectors into a pre-allocated view + * + * Like @ref positions2DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void positions2DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Positions as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Position as the first argument. Converts + * the position array from an arbitrary underlying type and returns it + * in a newly-allocated array. If the underlying type is two-component, + * the Z component is set to @cpp 0.0f @ce. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref positions3DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array positions3DAsArray(UnsignedInt id = 0) const; + + /** + * @brief Positions as 3D float vectors into a pre-allocated view + * + * Like @ref positions3DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void positions3DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Normals as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Normal as the first argument. Converts the + * normal array from an arbitrary underlying type and returns it in a + * newly-allocated array. Expects that the vertex format is *not* + * implementation-specific, in that case you can only access the + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref normalsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array normalsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Normals as 3D float vectors into a pre-allocated view + * + * Like @ref normalsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref vertexCount() + */ + void normalsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Texture coordinates as 2D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::TextureCoordinates as the first argument. + * Converts the texture coordinate array from an arbitrary underlying + * type and returns it in a newly-allocated array. Expects that the + * vertex format is *not* implementation-specific, in that case you can + * only access the attribute via the typeless + * @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref textureCoordinates2DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array textureCoordinates2DAsArray(UnsignedInt id = 0) const; + + /** + * @brief Texture coordinates as 2D float vectors into a pre-allocated view + * + * Like @ref textureCoordinates2DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void textureCoordinates2DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Colors as RGBA floats + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Color as the first argument. Converts the + * color array from an arbitrary underlying type and returns it in a + * newly-allocated array. If the underlying type is three-component, + * the alpha component is set to @cpp 1.0f @ce. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref colorsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array colorsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Colors as RGBA floats into a pre-allocated view + * + * Like @ref colorsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref vertexCount() + */ + void colorsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Release index data storage + * + * Releases the ownership of the index data array and resets internal + * index-related state to default. The mesh then behaves like + * it has zero indices (but it can still have a non-zero vertex count), + * however @ref indexData() still return a zero-sized non-null array so + * index offset calculation continues to work as expected. + * + * Note that the returned array has a custom no-op deleter when the + * data are not owned by the mesh, and while the returned array type is + * mutable, the actual memory might be not. + * @see @ref indexData(), @ref indexDataFlags() + */ + Containers::Array releaseIndexData(); + + /** + * @brief Release attribute data storage + * + * Releases the ownership of the attribute data array and resets + * internal attribute-related state to default. The mesh then behaves + * like if it has no attributes (but it can still have a non-zero + * vertex count). Note that the returned array has a custom no-op + * deleter when the data are not owned by the mesh, and while the + * returned array type is mutable, the actual memory might be not. + * Additionally, the returned @ref MeshAttributeData instances + * may have different data pointers and sizes than what's returned by + * the @ref attribute() and @ref attributeData(UnsignedInt) const + * accessors as some of them might be offset-only --- use this function + * only if you *really* know what are you doing. + * @see @ref attributeData(), @ref MeshAttributeData::isOffsetOnly() + */ + Containers::Array releaseAttributeData(); + + /** + * @brief Release vertex data storage + * + * Releases the ownership of the vertex data array and resets internal + * attribute-related state to default. The mesh then behaves like it + * has zero vertices (but it can still have a non-zero amount of + * attributes), however @ref vertexData() will still return a zero- + * sized non-null array so attribute offset calculation continues to + * work as expected. + * + * Note that the returned array has a custom no-op deleter when the + * data are not owned by the mesh, and while the returned array type is + * mutable, the actual memory might be not. + * @see @ref vertexData(), @ref vertexDataFlags() + */ + Containers::Array releaseVertexData(); + + /** + * @brief Importer-specific state + * + * See @ref AbstractImporter::importerState() for more information. + */ + const void* importerState() const { return _importerState; } + + private: + /* For custom deleter checks. Not done in the constructors here because + the restriction is pointless when used outside of plugin + implementations. */ + friend AbstractImporter; + + /* Internal helper that doesn't assert, unlike attributeId() */ + UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; + + /* Like attribute(), but returning just a 1D view */ + Containers::StridedArrayView1D attributeDataViewInternal(const MeshAttributeData& attribute) const; + + #ifndef CORRADE_NO_ASSERT + template bool checkAttributeTypeCompatibility(const MeshAttributeData& attribute, const char* prefix) const; + #endif + + /* GPUs don't currently support more than 32-bit index types / vertex + counts so this should be enough. Sanity check: + https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkIndexType.html */ + UnsignedInt _vertexCount; + MeshIndexType _indexType; + MeshPrimitive _primitive; + DataFlags _indexDataFlags, _vertexDataFlags; + const void* _importerState; + Containers::Array _indexData, _vertexData; + Containers::Array _attributes; + /* MeshIndexData are "unpacked" in order to avoid excessive padding */ + Containers::ArrayView _indices; +}; + +#if !defined(CORRADE_NO_ASSERT) || defined(CORRADE_GRACEFUL_ASSERT) +namespace Implementation { + /* LCOV_EXCL_START */ + template constexpr MeshIndexType meshIndexTypeFor() { + /* C++ why there isn't an obvious way to do such a thing?! */ + static_assert(sizeof(T) == 0, "unsupported index type"); + return {}; + } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedByte; } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedShort; } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedInt; } + + /* Implicit mapping from a format to enum (1:1) */ + template constexpr VertexFormat vertexFormatFor() { + /* C++ why there isn't an obvious way to do such a thing?! */ + static_assert(sizeof(T) == 0, "unsupported attribute type"); + return {}; + } + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(format) \ + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::format; } + _c(Float) + _c(Half) + _c(Double) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(Vector2) + _c(Vector2h) + _c(Vector2d) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3h) + _c(Vector3d) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4h) + _c(Vector4d) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + _c(Vector4ui) + _c(Vector4i) + #undef _c + #endif + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3h; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3ubNormalized; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3usNormalized; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4h; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4ubNormalized; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4usNormalized; } + + /* Check if enum is compatible with a format (1:n). Mostly just 1:1 mapping + tho, so reusing vertexFormatFor(), with a few exceptions. */ + template constexpr bool isVertexFormatCompatible(VertexFormat type) { + return vertexFormatFor() == type; + } + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(format_) \ + template<> constexpr bool isVertexFormatCompatible(VertexFormat format) { \ + return format == VertexFormat::format_ || \ + format == VertexFormat::format_ ## Normalized; \ + } + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + /* For Color[34]u[sb] we expect the format to be normalized, which is + handled by vertexFormatFor() properly already */ + #undef _c + #endif + /* LCOV_EXCL_STOP */ + + constexpr bool isVertexFormatCompatibleWithAttribute(MeshAttribute name, VertexFormat format) { + /* Double types intentionally not supported for any builtin attributes + right now -- only for custom types */ + return + /* Implementation-specific formats can be used for any attribute + (tho the access capabilities will be reduced) */ + isVertexFormatImplementationSpecific(format) || + /* Named attributes are restricted so we can decode them */ + (name == MeshAttribute::Position && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized || + format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ub || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3b || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3us || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector3s || + format == VertexFormat::Vector3sNormalized)) || + (name == MeshAttribute::Normal && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3sNormalized)) || + (name == MeshAttribute::Color && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector4 || + format == VertexFormat::Vector4h || + format == VertexFormat::Vector4ubNormalized || + format == VertexFormat::Vector4usNormalized)) || + (name == MeshAttribute::TextureCoordinates && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized)) || + /* Custom attributes can be anything */ + isMeshAttributeCustom(name); + } + + constexpr bool isAttributeArrayAllowed(MeshAttribute name) { + return isMeshAttributeCustom(name); + } +} +#endif + +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const UnsignedShort arraySize, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: + _data{data.data()}, _vertexCount{UnsignedInt(data.size())}, _format{format}, + /** @todo support zero / negative stride? would be hard to transfer to GL */ + _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000), + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << data.stride()), + Short(data.stride())) + }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) + }, _arraySize{(CORRADE_CONSTEXPR_ASSERT(!arraySize || Implementation::isAttributeArrayAllowed(name), + "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize) + }, _isOffsetOnly{(CORRADE_CONSTEXPR_ASSERT(!arraySize || !isVertexFormatImplementationSpecific(format), + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format"), false)} {} + +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride, UnsignedShort arraySize) noexcept: + _data{offset}, _vertexCount{vertexCount}, _format{format}, + /** @todo support zero / negative stride? would be hard to transfer to GL */ + _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(stride) & 0xffff8000), + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << stride), + Short(stride)) + }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) + }, _arraySize{(CORRADE_CONSTEXPR_ASSERT(!arraySize || Implementation::isAttributeArrayAllowed(name), + "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize) + }, _isOffsetOnly{(CORRADE_CONSTEXPR_ASSERT(!arraySize || !isVertexFormatImplementationSpecific(format), + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format"), true)} {} + +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), 0, data, nullptr} {} + +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), UnsignedShort(data.size()[1]), Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, (CORRADE_CONSTEXPR_ASSERT(data.stride()[1] == sizeof(T), "Trade::MeshAttributeData: second view dimension is not contiguous"), nullptr)} {} + +template Containers::ArrayView MeshData::indices() const { + Containers::StridedArrayView2D data = indices(); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, + "Trade::MeshData::indices(): improper type requested for" << _indexType, nullptr); + return Containers::arrayCast<1, const T>(data).asContiguous(); +} + +template Containers::ArrayView MeshData::mutableIndices() { + Containers::StridedArrayView2D data = mutableIndices(); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, + "Trade::MeshData::mutableIndices(): improper type requested for" << _indexType, nullptr); + return Containers::arrayCast<1, T>(data).asContiguous(); +} + +#ifndef CORRADE_NO_ASSERT +template bool MeshData::checkAttributeTypeCompatibility(const MeshAttributeData& attribute, const char* const prefix) const { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + prefix << "can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), false); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible::type>(attribute._format), + prefix << "improper type requested for" << attribute._name << "of format" << attribute._format, false); + CORRADE_ASSERT(std::is_array::value == !!attribute._arraySize, + prefix << "use T[] to access an array attribute", false); + return true; +} +#endif + +template Containers::StridedArrayView1D MeshData::attribute(const UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(_attributes[id], "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView2D::type> MeshData::attribute(const UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[id]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::mutableAttribute(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(_attributes[id], "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[id]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(_attributes[attributeFor(name, id)], "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView2D::type> MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(_attributes[attributeFor(name, id)], "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + +}} + +#endif diff --git a/src/Magnum/Trade/MeshData2D.cpp b/src/Magnum/Trade/MeshData2D.cpp index 253887aeda..b534d1b3eb 100644 --- a/src/Magnum/Trade/MeshData2D.cpp +++ b/src/Magnum/Trade/MeshData2D.cpp @@ -23,8 +23,12 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "MeshData2D.h" +#include + #include "Magnum/Math/Color.h" namespace Magnum { namespace Trade { @@ -33,19 +37,52 @@ MeshData2D::MeshData2D(const MeshPrimitive primitive, std::vector i CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData2D: no position array specified", ); } +#ifdef MAGNUM_BUILD_DEPRECATED +MeshData2D::MeshData2D(const MeshData& other): _primitive{other.primitive()}, _importerState{other.importerState()} { + /* Copy indices, if any */ + if(other.isIndexed()) { + _indices.resize(other.indexCount()); + other.indicesInto(_indices); + } + + /* Copy attributes */ + _positions.resize(other.attributeCount(MeshAttribute::Position)); + for(UnsignedInt i = 0; i != _positions.size(); ++i) { + _positions[i].resize(other.vertexCount()); + other.positions2DInto(_positions[i], i); + } + _textureCoords2D.resize(other.attributeCount(MeshAttribute::TextureCoordinates)); + for(UnsignedInt i = 0; i != _textureCoords2D.size(); ++i) { + _textureCoords2D[i].resize(other.vertexCount()); + other.textureCoordinates2DInto(_textureCoords2D[i], i); + } + _colors.resize(other.attributeCount(MeshAttribute::Color)); + for(UnsignedInt i = 0; i != _colors.size(); ++i) { + _colors[i].resize(other.vertexCount()); + other.colorsInto(_colors[i], i); + } + + CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData3D: no position array specified in MeshData", ); +} +#endif + +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns here */ MeshData2D::MeshData2D(MeshData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP MeshData2D::~MeshData2D() = default; +CORRADE_IGNORE_DEPRECATED_PUSH /* GCC why you warn on return and not on param */ MeshData2D& MeshData2D::operator=(MeshData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP std::vector& MeshData2D::indices() { CORRADE_ASSERT(isIndexed(), "Trade::MeshData2D::indices(): the mesh is not indexed", _indices); diff --git a/src/Magnum/Trade/MeshData2D.h b/src/Magnum/Trade/MeshData2D.h index 31f2c6d001..0bd12a8b87 100644 --- a/src/Magnum/Trade/MeshData2D.h +++ b/src/Magnum/Trade/MeshData2D.h @@ -25,14 +25,24 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::MeshData2D + * @m_deprecated_since_latest Use @ref Magnum/Trade/MeshData.h and the + * @ref Magnum::Trade::MeshData "MeshData" class instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include -#include "Magnum/Magnum.h" -#include "Magnum/Trade/visibility.h" +#include "Magnum/Trade/MeshData.h" + +#ifndef _MAGNUM_NO_DEPRECATED_MESHDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MeshData.h and the MeshData class instead") +#endif namespace Magnum { namespace Trade { @@ -49,9 +59,11 @@ directly to vertex positions: @snippet MagnumTrade.cpp MeshData2D-transform +@m_deprecated_since_latest Use @ref MeshData instead. + @see @ref AbstractImporter::mesh2D(), @ref MeshData3D */ -class MAGNUM_TRADE_EXPORT MeshData2D { +class CORRADE_DEPRECATED("use MeshData instead") MAGNUM_TRADE_EXPORT MeshData2D { public: /** * @brief Constructor @@ -67,6 +79,16 @@ class MAGNUM_TRADE_EXPORT MeshData2D { */ explicit MeshData2D(MeshPrimitive primitive, std::vector indices, std::vector> positions, std::vector> textureCoords2D, std::vector> colors, const void* importerState = nullptr); + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Construct from @ref MeshData + * @m_deprecated_since_latest Use @ref MeshData directly instead. + */ + /* No data moving can take place because std::vector is damn shitty + regarding memory ownership transfer, so it can well be a copy. */ + CORRADE_DEPRECATED("use MeshData directly instead") /*implicit*/ MeshData2D(const MeshData& other); + #endif + /** @brief Copying is not allowed */ MeshData2D(const MeshData2D&) = delete; @@ -172,5 +194,8 @@ class MAGNUM_TRADE_EXPORT MeshData2D { }; }} +#else +#error use Magnum/Trade/MeshData.h and the MeshData class instead +#endif #endif diff --git a/src/Magnum/Trade/MeshData3D.cpp b/src/Magnum/Trade/MeshData3D.cpp index 4556256df7..a3e990ae47 100644 --- a/src/Magnum/Trade/MeshData3D.cpp +++ b/src/Magnum/Trade/MeshData3D.cpp @@ -23,8 +23,13 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA + #include "MeshData3D.h" +#include + #include "Magnum/Math/Color.h" namespace Magnum { namespace Trade { @@ -33,19 +38,57 @@ MeshData3D::MeshData3D(const MeshPrimitive primitive, std::vector i CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData3D: no position array specified", ); } +#ifdef MAGNUM_BUILD_DEPRECATED +MeshData3D::MeshData3D(const MeshData& other): _primitive{other.primitive()}, _importerState{other.importerState()} { + /* Copy indices, if any */ + if(other.isIndexed()) { + _indices.resize(other.indexCount()); + other.indicesInto(_indices); + } + + /* Copy attributes */ + _positions.resize(other.attributeCount(MeshAttribute::Position)); + for(UnsignedInt i = 0; i != _positions.size(); ++i) { + _positions[i].resize(other.vertexCount()); + other.positions3DInto(_positions[i], i); + } + _normals.resize(other.attributeCount(MeshAttribute::Normal)); + for(UnsignedInt i = 0; i != _normals.size(); ++i) { + _normals[i].resize(other.vertexCount()); + other.normalsInto(_normals[i], i); + } + _textureCoords2D.resize(other.attributeCount(MeshAttribute::TextureCoordinates)); + for(UnsignedInt i = 0; i != _textureCoords2D.size(); ++i) { + _textureCoords2D[i].resize(other.vertexCount()); + other.textureCoordinates2DInto(_textureCoords2D[i], i); + } + _colors.resize(other.attributeCount(MeshAttribute::Color)); + for(UnsignedInt i = 0; i != _colors.size(); ++i) { + _colors[i].resize(other.vertexCount()); + other.colorsInto(_colors[i], i); + } + + CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData3D: no position array specified in MeshData", ); +} +#endif + +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns here */ MeshData3D::MeshData3D(MeshData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP MeshData3D::~MeshData3D() = default; +CORRADE_IGNORE_DEPRECATED_PUSH /* GCC why you warn on return and not on param */ MeshData3D& MeshData3D::operator=(MeshData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP std::vector& MeshData3D::indices() { CORRADE_ASSERT(isIndexed(), "Trade::MeshData3D::indices(): the mesh is not indexed", _indices); diff --git a/src/Magnum/Trade/MeshData3D.h b/src/Magnum/Trade/MeshData3D.h index 0dea375798..804ef8f383 100644 --- a/src/Magnum/Trade/MeshData3D.h +++ b/src/Magnum/Trade/MeshData3D.h @@ -25,14 +25,24 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::MeshData3D + * @m_deprecated_since_latest Use @ref Magnum/Trade/MeshData.h and the + * @ref Magnum::Trade::MeshData "MeshData" class instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include -#include "Magnum/Magnum.h" -#include "Magnum/Trade/visibility.h" +#include "Magnum/Trade/MeshData.h" + +#ifndef _MAGNUM_NO_DEPRECATED_MESHDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MeshData.h and the MeshData class instead") +#endif namespace Magnum { namespace Trade { @@ -49,9 +59,11 @@ to positions and normals: @snippet MagnumTrade.cpp MeshData3D-transform +@m_deprecated_since_latest Use @ref MeshData instead. + @see @ref AbstractImporter::mesh3D(), @ref MeshData2D */ -class MAGNUM_TRADE_EXPORT MeshData3D { +class CORRADE_DEPRECATED("use MeshData instead") MAGNUM_TRADE_EXPORT MeshData3D { public: /** * @brief Constructor @@ -68,6 +80,16 @@ class MAGNUM_TRADE_EXPORT MeshData3D { */ explicit MeshData3D(MeshPrimitive primitive, std::vector indices, std::vector> positions, std::vector> normals, std::vector> textureCoords2D, std::vector> colors, const void* importerState = nullptr); + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Construct from @ref MeshData + * @m_deprecated_since_latest Use @ref MeshData directly instead. + */ + /* No data moving can take place because std::vector is damn shitty + regarding memory ownership transfer, so it can well be a copy. */ + CORRADE_DEPRECATED("use MeshData directly instead") /*implicit*/ MeshData3D(const MeshData& other); + #endif + /** @brief Copying is not allowed */ MeshData3D(const MeshData3D&) = delete; @@ -189,5 +211,8 @@ class MAGNUM_TRADE_EXPORT MeshData3D { }; }} +#else +#error use Magnum/Trade/MeshData.h and the MeshData class instead +#endif #endif diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 60f4d699d1..0ca558f93a 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -34,17 +34,24 @@ #include "Magnum/FileCallback.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/ArrayAllocator.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshObjectData2D.h" #include "Magnum/Trade/MeshObjectData3D.h" #include "Magnum/Trade/PhongMaterialData.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/TextureData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + #include "configure.h" namespace Magnum { namespace Trade { namespace Test { namespace { @@ -105,6 +112,8 @@ struct AbstractImporterTest: TestSuite::Tester { void animationNotImplemented(); void animationNoFile(); void animationOutOfRange(); + void animationNonOwningDeleters(); + void animationGrowableDeleters(); void animationCustomDataDeleter(); void animationCustomTrackDeleter(); @@ -156,6 +165,36 @@ struct AbstractImporterTest: TestSuite::Tester { void object3DNoFile(); void object3DOutOfRange(); + void mesh(); + #ifdef MAGNUM_BUILD_DEPRECATED + void meshDeprecatedFallback(); + #endif + void meshCountNotImplemented(); + void meshCountNoFile(); + void meshLevelCountNotImplemented(); + void meshLevelCountNoFile(); + void meshLevelCountOutOfRange(); + void meshLevelCountZero(); + void meshForNameNotImplemented(); + void meshForNameNoFile(); + void meshNameNotImplemented(); + void meshNameNoFile(); + void meshNameOutOfRange(); + void meshNotImplemented(); + void meshNoFile(); + void meshOutOfRange(); + void meshLevelOutOfRange(); + void meshNonOwningDeleters(); + void meshGrowableDeleters(); + void meshCustomIndexDataDeleter(); + void meshCustomVertexDataDeleter(); + void meshCustomAttributesDeleter(); + + void meshAttributeName(); + void meshAttributeNameNotImplemented(); + void meshAttributeNameNotCustom(); + + #ifdef MAGNUM_BUILD_DEPRECATED void mesh2D(); void mesh2DCountNotImplemented(); void mesh2DCountNoFile(); @@ -179,6 +218,7 @@ struct AbstractImporterTest: TestSuite::Tester { void mesh3DNotImplemented(); void mesh3DNoFile(); void mesh3DOutOfRange(); + #endif void material(); void materialCountNotImplemented(); @@ -220,6 +260,8 @@ struct AbstractImporterTest: TestSuite::Tester { void image1DNoFile(); void image1DOutOfRange(); void image1DLevelOutOfRange(); + void image1DNonOwningDeleter(); + void image1DGrowableDeleter(); void image1DCustomDeleter(); void image2D(); @@ -238,6 +280,8 @@ struct AbstractImporterTest: TestSuite::Tester { void image2DNoFile(); void image2DOutOfRange(); void image2DLevelOutOfRange(); + void image2DNonOwningDeleter(); + void image2DGrowableDeleter(); void image2DCustomDeleter(); void image3D(); @@ -256,6 +300,8 @@ struct AbstractImporterTest: TestSuite::Tester { void image3DNoFile(); void image3DOutOfRange(); void image3DLevelOutOfRange(); + void image3DNonOwningDeleter(); + void image3DGrowableDeleter(); void image3DCustomDeleter(); void importerState(); @@ -320,6 +366,8 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::animationNotImplemented, &AbstractImporterTest::animationNoFile, &AbstractImporterTest::animationOutOfRange, + &AbstractImporterTest::animationNonOwningDeleters, + &AbstractImporterTest::animationGrowableDeleters, &AbstractImporterTest::animationCustomDataDeleter, &AbstractImporterTest::animationCustomTrackDeleter, @@ -371,6 +419,36 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::object3DNoFile, &AbstractImporterTest::object3DOutOfRange, + &AbstractImporterTest::mesh, + #ifdef MAGNUM_BUILD_DEPRECATED + &AbstractImporterTest::meshDeprecatedFallback, + #endif + &AbstractImporterTest::meshCountNotImplemented, + &AbstractImporterTest::meshCountNoFile, + &AbstractImporterTest::meshLevelCountNotImplemented, + &AbstractImporterTest::meshLevelCountNoFile, + &AbstractImporterTest::meshLevelCountOutOfRange, + &AbstractImporterTest::meshLevelCountZero, + &AbstractImporterTest::meshForNameNotImplemented, + &AbstractImporterTest::meshForNameNoFile, + &AbstractImporterTest::meshNameNotImplemented, + &AbstractImporterTest::meshNameNoFile, + &AbstractImporterTest::meshNameOutOfRange, + &AbstractImporterTest::meshNotImplemented, + &AbstractImporterTest::meshNoFile, + &AbstractImporterTest::meshOutOfRange, + &AbstractImporterTest::meshLevelOutOfRange, + &AbstractImporterTest::meshNonOwningDeleters, + &AbstractImporterTest::meshGrowableDeleters, + &AbstractImporterTest::meshCustomIndexDataDeleter, + &AbstractImporterTest::meshCustomVertexDataDeleter, + &AbstractImporterTest::meshCustomAttributesDeleter, + + &AbstractImporterTest::meshAttributeName, + &AbstractImporterTest::meshAttributeNameNotImplemented, + &AbstractImporterTest::meshAttributeNameNotCustom, + + #ifdef MAGNUM_BUILD_DEPRECATED &AbstractImporterTest::mesh2D, &AbstractImporterTest::mesh2DCountNotImplemented, &AbstractImporterTest::mesh2DCountNoFile, @@ -394,6 +472,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::mesh3DNotImplemented, &AbstractImporterTest::mesh3DNoFile, &AbstractImporterTest::mesh3DOutOfRange, + #endif &AbstractImporterTest::material, &AbstractImporterTest::materialCountNotImplemented, @@ -435,6 +514,8 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image1DNoFile, &AbstractImporterTest::image1DOutOfRange, &AbstractImporterTest::image1DLevelOutOfRange, + &AbstractImporterTest::image1DNonOwningDeleter, + &AbstractImporterTest::image1DGrowableDeleter, &AbstractImporterTest::image1DCustomDeleter, &AbstractImporterTest::image2D, @@ -453,6 +534,8 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image2DNoFile, &AbstractImporterTest::image2DOutOfRange, &AbstractImporterTest::image2DLevelOutOfRange, + &AbstractImporterTest::image2DNonOwningDeleter, + &AbstractImporterTest::image2DGrowableDeleter, &AbstractImporterTest::image2DCustomDeleter, &AbstractImporterTest::image3D, @@ -471,6 +554,8 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image3DNoFile, &AbstractImporterTest::image3DOutOfRange, &AbstractImporterTest::image3DLevelOutOfRange, + &AbstractImporterTest::image3DNonOwningDeleter, + &AbstractImporterTest::image3DGrowableDeleter, &AbstractImporterTest::image3DCustomDeleter, &AbstractImporterTest::importerState, @@ -1022,9 +1107,18 @@ void AbstractImporterTest::scene() { CORRADE_COMPARE(importer.sceneForName("eighth"), 7); CORRADE_COMPARE(importer.sceneName(7), "eighth"); - auto data = importer.scene(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.scene(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.scene("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.scene("foo")); + } } void AbstractImporterTest::sceneCountNotImplemented() { @@ -1144,7 +1238,10 @@ void AbstractImporterTest::sceneNoFile() { Error redirectError{&out}; importer.scene(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::scene(): no file opened\n"); + importer.scene("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::scene(): no file opened\n" + "Trade::AbstractImporter::scene(): no file opened\n"); } void AbstractImporterTest::sceneOutOfRange() { @@ -1179,7 +1276,12 @@ void AbstractImporterTest::animation() { else return {}; } Containers::Optional doAnimation(UnsignedInt id) override { - if(id == 7) return AnimationData{{}, {}, &state}; + /* Verify that initializer list is converted to an array with + the default deleter and not something disallowed */ + if(id == 7) return AnimationData{nullptr, { + AnimationTrackData{AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} + }, &state}; else return AnimationData{{}, {}}; } } importer; @@ -1188,9 +1290,18 @@ void AbstractImporterTest::animation() { CORRADE_COMPARE(importer.animationForName("eighth"), 7); CORRADE_COMPARE(importer.animationName(7), "eighth"); - auto data = importer.animation(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.animation(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.animation("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.animation("foo")); + } } void AbstractImporterTest::animationCountNotImplemented() { @@ -1310,7 +1421,10 @@ void AbstractImporterTest::animationNoFile() { Error redirectError{&out}; importer.animation(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): no file opened\n"); + importer.animation("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::animation(): no file opened\n" + "Trade::AbstractImporter::animation(): no file opened\n"); } void AbstractImporterTest::animationOutOfRange() { @@ -1329,6 +1443,47 @@ void AbstractImporterTest::animationOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::animationNonOwningDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 1; } + Containers::Optional doAnimation(UnsignedInt) override { + return AnimationData{Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}, + Containers::Array{&track, 1, + reinterpret_cast(Implementation::nonOwnedArrayDeleter)}}; + } + + char data[1]; + AnimationTrackData track; + } importer; + + auto data = importer.animation(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + +void AbstractImporterTest::animationGrowableDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 1; } + Containers::Optional doAnimation(UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\x37'); + return AnimationData{std::move(data), {AnimationTrackData{}}}; + } + } importer; + + auto data = importer.animation(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\x37'); +} + void AbstractImporterTest::animationCustomDataDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -1336,6 +1491,7 @@ void AbstractImporterTest::animationCustomDataDeleter() { void doClose() override {} UnsignedInt doAnimationCount() const override { return 1; } + Int doAnimationForName(const std::string&) override { return 0; } Containers::Optional doAnimation(UnsignedInt) override { return AnimationData{Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, nullptr}; } @@ -1345,7 +1501,10 @@ void AbstractImporterTest::animationCustomDataDeleter() { Error redirectError{&out}; importer.animation(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); + importer.animation(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::animationCustomTrackDeleter() { @@ -1355,6 +1514,7 @@ void AbstractImporterTest::animationCustomTrackDeleter() { void doClose() override {} UnsignedInt doAnimationCount() const override { return 1; } + Int doAnimationForName(const std::string&) override { return 0; } Containers::Optional doAnimation(UnsignedInt) override { return AnimationData{nullptr, Containers::Array{nullptr, 0, [](AnimationTrackData*, std::size_t) {}}}; } @@ -1364,7 +1524,10 @@ void AbstractImporterTest::animationCustomTrackDeleter() { Error redirectError{&out}; importer.animation(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); + importer.animation(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::light() { @@ -1392,9 +1555,18 @@ void AbstractImporterTest::light() { CORRADE_COMPARE(importer.lightForName("eighth"), 7); CORRADE_COMPARE(importer.lightName(7), "eighth"); - auto data = importer.light(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.light(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.light("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.light("foo")); + } } void AbstractImporterTest::lightCountNotImplemented() { @@ -1514,7 +1686,10 @@ void AbstractImporterTest::lightNoFile() { Error redirectError{&out}; importer.light(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::light(): no file opened\n"); + importer.light("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::light(): no file opened\n" + "Trade::AbstractImporter::light(): no file opened\n"); } void AbstractImporterTest::lightOutOfRange() { @@ -1558,9 +1733,18 @@ void AbstractImporterTest::camera() { CORRADE_COMPARE(importer.cameraForName("eighth"), 7); CORRADE_COMPARE(importer.cameraName(7), "eighth"); - auto data = importer.camera(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.camera(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.camera("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.camera("foo")); + } } void AbstractImporterTest::cameraCountNotImplemented() { @@ -1680,7 +1864,10 @@ void AbstractImporterTest::cameraNoFile() { Error redirectError{&out}; importer.camera(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::camera(): no file opened\n"); + importer.camera("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::camera(): no file opened\n" + "Trade::AbstractImporter::camera(): no file opened\n"); } void AbstractImporterTest::cameraOutOfRange() { @@ -1724,9 +1911,18 @@ void AbstractImporterTest::object2D() { CORRADE_COMPARE(importer.object2DForName("eighth"), 7); CORRADE_COMPARE(importer.object2DName(7), "eighth"); - auto data = importer.object2D(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.object2D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.object2D("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.object2D("foo")); + } } void AbstractImporterTest::object2DCountNotImplemented() { @@ -1846,7 +2042,10 @@ void AbstractImporterTest::object2DNoFile() { Error redirectError{&out}; importer.object2D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2D(): no file opened\n"); + importer.object2D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object2D(): no file opened\n" + "Trade::AbstractImporter::object2D(): no file opened\n"); } void AbstractImporterTest::object2DOutOfRange() { @@ -1890,9 +2089,18 @@ void AbstractImporterTest::object3D() { CORRADE_COMPARE(importer.object3DForName("eighth"), 7); CORRADE_COMPARE(importer.object3DName(7), "eighth"); - auto data = importer.object3D(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.object3D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.object3D("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.object3D("foo")); + } } void AbstractImporterTest::object3DCountNotImplemented() { @@ -2012,7 +2220,10 @@ void AbstractImporterTest::object3DNoFile() { Error redirectError{&out}; importer.object3D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): no file opened\n"); + importer.object3D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object3D(): no file opened\n" + "Trade::AbstractImporter::object3D(): no file opened\n"); } void AbstractImporterTest::object3DOutOfRange() { @@ -2031,6 +2242,492 @@ void AbstractImporterTest::object3DOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::mesh() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + UnsignedInt doMeshLevelCount(UnsignedInt id) override { + if(id == 7) return 3; + else return {}; + } + Int doMeshForName(const std::string& name) override { + if(name == "eighth") return 7; + else return -1; + } + std::string doMeshName(UnsignedInt id) override { + if(id == 7) return "eighth"; + else return {}; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + /* Verify that initializer list is converted to an array with + the default deleter and not something disallowed */ + if(id == 7 && level == 2) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, MeshData::ImplicitVertexCount, &state}; + else return {}; + } + } importer; + + CORRADE_COMPARE(importer.meshCount(), 8); + CORRADE_COMPARE(importer.meshForName("eighth"), 7); + CORRADE_COMPARE(importer.meshName(7), "eighth"); + + { + auto data = importer.mesh(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.mesh("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.mesh("foo")); + } +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImporterTest::meshDeprecatedFallback() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string& name) override { + if(name == "eighth") return 7; + else return -1; + } + std::string doMeshName(UnsignedInt id) override { + if(id == 7) return "eighth"; + else return {}; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + if(id == 7 && level == 0) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, MeshData::ImplicitVertexCount, &state}; + else return {}; + } + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + /* Nothing done for 2D as there were no known importers for these */ + CORRADE_COMPARE(importer.mesh2DCount(), 0); + CORRADE_COMPARE(importer.mesh2DForName("eighth"), -1); + + /* For 3D it's called through */ + CORRADE_COMPARE(importer.mesh3DCount(), 8); + CORRADE_COMPARE(importer.mesh3DForName("eighth"), 7); + CORRADE_COMPARE(importer.mesh3DName(7), "eighth"); + + auto data = importer.mesh3D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + CORRADE_IGNORE_DEPRECATED_POP +} +#endif + +void AbstractImporterTest::meshCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshCount(), 0); +} + +void AbstractImporterTest::meshCountNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshCount(); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshCount(): no file opened\n"); +} + +void AbstractImporterTest::meshLevelCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.meshLevelCount(7), 1); +} + +void AbstractImporterTest::meshLevelCountNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshLevelCount(): no file opened\n"); +} + +void AbstractImporterTest::meshLevelCountOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshLevelCount(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshLevelCountZero() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string&) override { return 0; } + UnsignedInt doMeshLevelCount(UnsignedInt) override { return 0; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(7); + /* This should print a similar message instead of a confusing + "level 1 out of range for 0 entries" */ + importer.mesh(7, 1); + importer.mesh("", 1); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::meshLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::mesh(): implementation reported zero levels\n" + "Trade::AbstractImporter::mesh(): implementation reported zero levels\n"); +} + +void AbstractImporterTest::meshForNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshForName(""), -1); +} + +void AbstractImporterTest::meshForNameNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshForName(""); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshForName(): no file opened\n"); +} + +void AbstractImporterTest::meshNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.meshName(7), ""); +} + +void AbstractImporterTest::meshNameNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshName(42); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshName(): no file opened\n"); +} + +void AbstractImporterTest::meshNameOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshName(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshName(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): not implemented\n"); +} + +void AbstractImporterTest::meshNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(42); + importer.mesh("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): no file opened\n" + "Trade::AbstractImporter::mesh(): no file opened\n"); +} + +void AbstractImporterTest::meshOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshLevelOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string&) override { return 0; } + UnsignedInt doMeshLevelCount(UnsignedInt) override { return 3; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.mesh(7, 3); + importer.mesh("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::mesh(): level 3 out of range for 3 entries\n"); +} + +void AbstractImporterTest::meshNonOwningDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, + Containers::Array{indexData, 1, Implementation::nonOwnedArrayDeleter}, MeshIndexData{MeshIndexType::UnsignedByte, indexData}, + Containers::Array{nullptr, 0, Implementation::nonOwnedArrayDeleter}, + meshAttributeDataNonOwningArray(attributes)}; + } + + char indexData[1]; + MeshAttributeData attributes[1]{ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr} + }; + } importer; + + auto data = importer.mesh(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->indexData()), importer.indexData); +} + +void AbstractImporterTest::meshGrowableDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + Containers::Array indexData; + Containers::arrayAppend(indexData, '\xab'); + Containers::Array vertexData; + Containers::arrayAppend(vertexData, Vector3{}); + MeshIndexData indices{MeshIndexType::UnsignedByte, indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + return MeshData{MeshPrimitive::Triangles, + std::move(indexData), indices, + Containers::arrayAllocatorCast(std::move(vertexData)), {positions}}; + } + } importer; + + auto data = importer.mesh(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->indexData()[0], '\xab'); + CORRADE_COMPARE(data->vertexData().size(), 12); +} + +void AbstractImporterTest::meshCustomIndexDataDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Int doMeshForName(const std::string&) override { return 0; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1}; + } + + char data[1]; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + importer.mesh(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshCustomVertexDataDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Int doMeshForName(const std::string&) override { return 0; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}}; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + importer.mesh(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshCustomAttributesDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Int doMeshForName(const std::string&) override { return 0; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, nullptr, Containers::Array{&positions, 1, [](MeshAttributeData*, std::size_t) {}}}; + } + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + importer.mesh(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + ); +} + +void AbstractImporterTest::meshAttributeName() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + MeshAttribute doMeshAttributeForName(const std::string& name) override { + if(name == "SMOOTH_GROUP_ID") return meshAttributeCustom(37); + return MeshAttribute{}; + } + + std::string doMeshAttributeName(UnsignedShort id) override { + if(id == 37) return "SMOOTH_GROUP_ID"; + return ""; + } + } importer; + + CORRADE_COMPARE(importer.meshAttributeForName("SMOOTH_GROUP_ID"), meshAttributeCustom(37)); + CORRADE_COMPARE(importer.meshAttributeName(meshAttributeCustom(37)), "SMOOTH_GROUP_ID"); +} + +void AbstractImporterTest::meshAttributeNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshAttributeForName(""), MeshAttribute{}); + CORRADE_COMPARE(importer.meshAttributeName(meshAttributeCustom(37)), ""); +} + +void AbstractImporterTest::meshAttributeNameNotCustom() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + MeshAttribute doMeshAttributeForName(const std::string&) override { + return MeshAttribute::Position; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshAttributeForName("SMOOTH_GROUP_ID"); + importer.meshAttributeName(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::meshAttributeForName(): implementation-returned Trade::MeshAttribute::Position is neither custom nor invalid\n" + "Trade::AbstractImporter::meshAttributeName(): Trade::MeshAttribute::Position is not custom\n"); +} + +#ifdef MAGNUM_BUILD_DEPRECATED void AbstractImporterTest::mesh2D() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2046,12 +2743,15 @@ void AbstractImporterTest::mesh2D() { if(id == 7) return "eighth"; else return {}; } + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional doMesh2D(UnsignedInt id) override { if(id == 7) return MeshData2D{{}, {}, {{}}, {}, {}, &state}; else return {}; } + CORRADE_IGNORE_DEPRECATED_POP } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DCount(), 8); CORRADE_COMPARE(importer.mesh2DForName("eighth"), 7); CORRADE_COMPARE(importer.mesh2DName(7), "eighth"); @@ -2059,6 +2759,7 @@ void AbstractImporterTest::mesh2D() { auto data = importer.mesh2D(7); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DCountNotImplemented() { @@ -2068,7 +2769,9 @@ void AbstractImporterTest::mesh2DCountNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DCountNoFile() { @@ -2081,7 +2784,9 @@ void AbstractImporterTest::mesh2DCountNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DCount(); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DCount(): no file opened\n"); } @@ -2092,7 +2797,9 @@ void AbstractImporterTest::mesh2DForNameNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DForName(""), -1); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DForNameNoFile() { @@ -2105,7 +2812,9 @@ void AbstractImporterTest::mesh2DForNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DForName(""); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DForName(): no file opened\n"); } @@ -2118,7 +2827,9 @@ void AbstractImporterTest::mesh2DNameNotImplemented() { UnsignedInt doMesh2DCount() const override { return 8; } } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DName(7), ""); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DNameNoFile() { @@ -2131,7 +2842,9 @@ void AbstractImporterTest::mesh2DNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DName(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DName(): no file opened\n"); } @@ -2147,7 +2860,9 @@ void AbstractImporterTest::mesh2DNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DName(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DName(): index 8 out of range for 8 entries\n"); } @@ -2163,7 +2878,9 @@ void AbstractImporterTest::mesh2DNotImplemented() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2D(7); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2D(): not implemented\n"); } @@ -2177,7 +2894,9 @@ void AbstractImporterTest::mesh2DNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2D(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2D(): no file opened\n"); } @@ -2193,7 +2912,9 @@ void AbstractImporterTest::mesh2DOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2D(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2D(): index 8 out of range for 8 entries\n"); } @@ -2212,12 +2933,15 @@ void AbstractImporterTest::mesh3D() { if(id == 7) return "eighth"; else return {}; } + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional doMesh3D(UnsignedInt id) override { if(id == 7) return MeshData3D{{}, {}, {{}}, {}, {}, {}, &state}; else return {}; } + CORRADE_IGNORE_DEPRECATED_POP } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DCount(), 8); CORRADE_COMPARE(importer.mesh3DForName("eighth"), 7); CORRADE_COMPARE(importer.mesh3DName(7), "eighth"); @@ -2225,6 +2949,7 @@ void AbstractImporterTest::mesh3D() { auto data = importer.mesh3D(7); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DCountNotImplemented() { @@ -2234,7 +2959,9 @@ void AbstractImporterTest::mesh3DCountNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DCountNoFile() { @@ -2247,7 +2974,9 @@ void AbstractImporterTest::mesh3DCountNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DCount(); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DCount(): no file opened\n"); } @@ -2258,7 +2987,9 @@ void AbstractImporterTest::mesh3DForNameNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DForName(""), -1); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DForNameNoFile() { @@ -2271,7 +3002,9 @@ void AbstractImporterTest::mesh3DForNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DForName(""); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DForName(): no file opened\n"); } @@ -2284,7 +3017,9 @@ void AbstractImporterTest::mesh3DNameNotImplemented() { UnsignedInt doMesh3DCount() const override { return 8; } } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DName(7), ""); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DNameNoFile() { @@ -2297,7 +3032,9 @@ void AbstractImporterTest::mesh3DNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DName(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DName(): no file opened\n"); } @@ -2313,7 +3050,9 @@ void AbstractImporterTest::mesh3DNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DName(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DName(): index 8 out of range for 8 entries\n"); } @@ -2329,8 +3068,12 @@ void AbstractImporterTest::mesh3DNotImplemented() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3D(7); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3D(): not implemented\n"); + CORRADE_IGNORE_DEPRECATED_POP + /* Not mesh3D() because this one delegates into mesh() for backwards + compatibility */ + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): not implemented\n"); } void AbstractImporterTest::mesh3DNoFile() { @@ -2343,7 +3086,9 @@ void AbstractImporterTest::mesh3DNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3D(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3D(): no file opened\n"); } @@ -2359,9 +3104,12 @@ void AbstractImporterTest::mesh3DOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3D(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3D(): index 8 out of range for 8 entries\n"); } +#endif void AbstractImporterTest::material() { struct: AbstractImporter { @@ -2388,9 +3136,18 @@ void AbstractImporterTest::material() { CORRADE_COMPARE(importer.materialForName("eighth"), 7); CORRADE_COMPARE(importer.materialName(7), "eighth"); - auto data = importer.material(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.material(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.material("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.material("foo")); + } } void AbstractImporterTest::materialCountNotImplemented() { @@ -2510,7 +3267,10 @@ void AbstractImporterTest::materialNoFile() { Error redirectError{&out}; importer.material(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::material(): no file opened\n"); + importer.material("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::material(): no file opened\n" + "Trade::AbstractImporter::material(): no file opened\n"); } void AbstractImporterTest::materialOutOfRange() { @@ -2554,9 +3314,18 @@ void AbstractImporterTest::texture() { CORRADE_COMPARE(importer.textureForName("eighth"), 7); CORRADE_COMPARE(importer.textureName(7), "eighth"); - auto data = importer.texture(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.texture(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.texture("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.texture("foo")); + } } void AbstractImporterTest::textureCountNotImplemented() { @@ -2676,7 +3445,10 @@ void AbstractImporterTest::textureNoFile() { Error redirectError{&out}; importer.texture(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::texture(): no file opened\n"); + importer.texture("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::texture(): no file opened\n" + "Trade::AbstractImporter::texture(): no file opened\n"); } void AbstractImporterTest::textureOutOfRange() { @@ -2725,9 +3497,18 @@ void AbstractImporterTest::image1D() { CORRADE_COMPARE(importer.image1DForName("eighth"), 7); CORRADE_COMPARE(importer.image1DName(7), "eighth"); - auto data = importer.image1D(7, 2); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.image1D(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.image1D("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.image1D("foo")); + } } void AbstractImporterTest::image1DCountNotImplemented() { @@ -2801,6 +3582,7 @@ void AbstractImporterTest::image1DLevelCountZero() { void doClose() override {} UnsignedInt doImage1DCount() const override { return 8; } + Int doImage1DForName(const std::string&) override { return 0; } UnsignedInt doImage1DLevelCount(UnsignedInt) override { return 0; } } importer; @@ -2810,8 +3592,10 @@ void AbstractImporterTest::image1DLevelCountZero() { /* This should print a similar message instead of a confusing "level 1 out of range for 0 entries" */ importer.image1D(7, 1); + importer.image1D("", 1); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1DLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::image1D(): implementation reported zero levels\n" "Trade::AbstractImporter::image1D(): implementation reported zero levels\n"); } @@ -2908,7 +3692,10 @@ void AbstractImporterTest::image1DNoFile() { Error redirectError{&out}; importer.image1D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): no file opened\n"); + importer.image1D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image1D(): no file opened\n" + "Trade::AbstractImporter::image1D(): no file opened\n"); } void AbstractImporterTest::image1DOutOfRange() { @@ -2934,13 +3721,55 @@ void AbstractImporterTest::image1DLevelOutOfRange() { void doClose() override {} UnsignedInt doImage1DCount() const override { return 8; } + Int doImage1DForName(const std::string&) override { return 0; } UnsignedInt doImage1DLevelCount(UnsignedInt) override { return 3; } } importer; std::ostringstream out; Error redirectError{&out}; importer.image1D(7, 3); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n"); + importer.image1D("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n"); +} + +void AbstractImporterTest::image1DNonOwningDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + return ImageData1D{PixelFormat::RGBA8Unorm, {}, Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}}; + } + + char data[1]; + } importer; + + auto data = importer.image1D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + +void AbstractImporterTest::image1DGrowableDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\xff'); + return ImageData1D{PixelFormat::RGBA8Unorm, {}, std::move(data)}; + } + } importer; + + auto data = importer.image1D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\xff'); } void AbstractImporterTest::image1DCustomDeleter() { @@ -2950,6 +3779,7 @@ void AbstractImporterTest::image1DCustomDeleter() { void doClose() override {} UnsignedInt doImage1DCount() const override { return 1; } + Int doImage1DForName(const std::string&) override { return 0; } Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { return ImageData1D{PixelFormat::RGBA8Unorm, {}, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}}; } @@ -2959,7 +3789,10 @@ void AbstractImporterTest::image1DCustomDeleter() { Error redirectError{&out}; importer.image1D(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n"); + importer.image1D(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::image2D() { @@ -2992,9 +3825,18 @@ void AbstractImporterTest::image2D() { CORRADE_COMPARE(importer.image2DForName("eighth"), 7); CORRADE_COMPARE(importer.image2DName(7), "eighth"); - auto data = importer.image2D(7, 2); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.image2D(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.image2D("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.image2D("foo")); + } } void AbstractImporterTest::image2DCountNotImplemented() { @@ -3068,6 +3910,7 @@ void AbstractImporterTest::image2DLevelCountZero() { void doClose() override {} UnsignedInt doImage2DCount() const override { return 8; } + Int doImage2DForName(const std::string&) override { return 0; } UnsignedInt doImage2DLevelCount(UnsignedInt) override { return 0; } } importer; @@ -3077,8 +3920,10 @@ void AbstractImporterTest::image2DLevelCountZero() { /* This should print a similar message instead of a confusing "level 1 out of range for 0 entries" */ importer.image2D(7, 1); + importer.image2D(7, 1); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2DLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::image2D(): implementation reported zero levels\n" "Trade::AbstractImporter::image2D(): implementation reported zero levels\n"); } @@ -3175,7 +4020,10 @@ void AbstractImporterTest::image2DNoFile() { Error redirectError{&out}; importer.image2D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): no file opened\n"); + importer.image2D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image2D(): no file opened\n" + "Trade::AbstractImporter::image2D(): no file opened\n"); } void AbstractImporterTest::image2DOutOfRange() { @@ -3201,13 +4049,55 @@ void AbstractImporterTest::image2DLevelOutOfRange() { void doClose() override {} UnsignedInt doImage2DCount() const override { return 8; } + Int doImage2DForName(const std::string&) override { return 0; } UnsignedInt doImage2DLevelCount(UnsignedInt) override { return 3; } } importer; std::ostringstream out; Error redirectError{&out}; importer.image2D(7, 3); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n"); + importer.image2D("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n"); +} + +void AbstractImporterTest::image2DNonOwningDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage2DCount() const override { return 1; } + Containers::Optional doImage2D(UnsignedInt, UnsignedInt) override { + return ImageData2D{PixelFormat::RGBA8Unorm, {}, Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}}; + } + + char data[1]; + } importer; + + auto data = importer.image2D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + +void AbstractImporterTest::image2DGrowableDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage2DCount() const override { return 1; } + Containers::Optional doImage2D(UnsignedInt, UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\xff'); + return ImageData2D{PixelFormat::RGBA8Unorm, {}, std::move(data)}; + } + } importer; + + auto data = importer.image2D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\xff'); } void AbstractImporterTest::image2DCustomDeleter() { @@ -3217,6 +4107,7 @@ void AbstractImporterTest::image2DCustomDeleter() { void doClose() override {} UnsignedInt doImage2DCount() const override { return 1; } + Int doImage2DForName(const std::string&) override { return 0; } Containers::Optional doImage2D(UnsignedInt, UnsignedInt) override { return ImageData2D{PixelFormat::RGBA8Unorm, {}, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}}; } @@ -3226,7 +4117,10 @@ void AbstractImporterTest::image2DCustomDeleter() { Error redirectError{&out}; importer.image2D(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n"); + importer.image2D(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::image3D() { @@ -3259,9 +4153,18 @@ void AbstractImporterTest::image3D() { CORRADE_COMPARE(importer.image3DForName("eighth"), 7); CORRADE_COMPARE(importer.image3DName(7), "eighth"); - auto data = importer.image3D(7, 2); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.image3D(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.image3D("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.image3D("foo")); + } } void AbstractImporterTest::image3DCountNotImplemented() { @@ -3336,6 +4239,7 @@ void AbstractImporterTest::image3DLevelCountZero() { void doClose() override {} UnsignedInt doImage3DCount() const override { return 8; } + Int doImage3DForName(const std::string&) override { return 0; } UnsignedInt doImage3DLevelCount(UnsignedInt) override { return 0; } } importer; @@ -3345,8 +4249,10 @@ void AbstractImporterTest::image3DLevelCountZero() { /* This should print a similar message instead of a confusing "level 1 out of range for 0 entries" */ importer.image3D(7, 1); + importer.image3D("", 1); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3DLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::image3D(): implementation reported zero levels\n" "Trade::AbstractImporter::image3D(): implementation reported zero levels\n"); } @@ -3443,7 +4349,10 @@ void AbstractImporterTest::image3DNoFile() { Error redirectError{&out}; importer.image3D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): no file opened\n"); + importer.image3D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image3D(): no file opened\n" + "Trade::AbstractImporter::image3D(): no file opened\n"); } void AbstractImporterTest::image3DOutOfRange() { @@ -3469,13 +4378,55 @@ void AbstractImporterTest::image3DLevelOutOfRange() { void doClose() override {} UnsignedInt doImage3DCount() const override { return 8; } + Int doImage3DForName(const std::string&) override { return 0; } UnsignedInt doImage3DLevelCount(UnsignedInt) override { return 3; } } importer; std::ostringstream out; Error redirectError{&out}; importer.image3D(7, 3); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n"); + importer.image3D("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n"); +} + +void AbstractImporterTest::image3DNonOwningDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage3DCount() const override { return 1; } + Containers::Optional doImage3D(UnsignedInt, UnsignedInt) override { + return ImageData3D{PixelFormat::RGBA8Unorm, {}, Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}}; + } + + char data[1]; + } importer; + + auto data = importer.image3D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + +void AbstractImporterTest::image3DGrowableDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage3DCount() const override { return 1; } + Containers::Optional doImage3D(UnsignedInt, UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\xff'); + return ImageData3D{PixelFormat::RGBA8Unorm, {}, std::move(data)}; + } + } importer; + + auto data = importer.image3D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\xff'); } void AbstractImporterTest::image3DCustomDeleter() { @@ -3485,6 +4436,7 @@ void AbstractImporterTest::image3DCustomDeleter() { void doClose() override {} UnsignedInt doImage3DCount() const override { return 1; } + Int doImage3DForName(const std::string&) override { return 0; } Containers::Optional doImage3D(UnsignedInt, UnsignedInt) override { return ImageData3D{PixelFormat::RGBA8Unorm, {}, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}}; } @@ -3494,7 +4446,10 @@ void AbstractImporterTest::image3DCustomDeleter() { Error redirectError{&out}; importer.image3D(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n"); + importer.image3D(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::importerState() { diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index 11258c7fb4..6cf570c5ef 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -27,6 +27,7 @@ #include #include +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/Quaternion.h" #include "Magnum/Trade/AnimationData.h" @@ -35,48 +36,134 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct AnimationDataTest: TestSuite::Tester { explicit AnimationDataTest(); + void constructTrackData(); + void constructTrackDataResultType(); + void constructTrackDataTemplate(); + void constructTrackDataDefault(); + void construct(); + void constructNotOwned(); void constructImplicitDuration(); void constructImplicitDurationEmpty(); + void constructImplicitDurationNotOwned(); + void constructNotOwnedFlagOwned(); + void constructImplicitDurationNotOwnedFlagOwned(); + void constructCopy(); void constructMove(); - void constructTrackDataDefault(); + void mutableAccessNotAllowed(); void trackCustomResultType(); - void trackWrongIndex(); void trackWrongType(); void trackWrongResultType(); + void release(); + void debugAnimationTrackType(); void debugAnimationTrackTargetType(); }; +struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable}, +}; + AnimationDataTest::AnimationDataTest() { - addTests({&AnimationDataTest::construct, + addTests({&AnimationDataTest::constructTrackData, + &AnimationDataTest::constructTrackDataResultType, + &AnimationDataTest::constructTrackDataTemplate, + &AnimationDataTest::constructTrackDataDefault, + + &AnimationDataTest::construct, &AnimationDataTest::constructImplicitDuration, - &AnimationDataTest::constructImplicitDurationEmpty, + &AnimationDataTest::constructImplicitDurationEmpty}); + + addInstancedTests({&AnimationDataTest::constructNotOwned, + &AnimationDataTest::constructImplicitDurationNotOwned}, + Containers::arraySize(NotOwnedData)); + + addTests({&AnimationDataTest::constructNotOwnedFlagOwned, + &AnimationDataTest::constructImplicitDurationNotOwnedFlagOwned, + &AnimationDataTest::constructCopy, &AnimationDataTest::constructMove, - &AnimationDataTest::constructTrackDataDefault, + &AnimationDataTest::mutableAccessNotAllowed, &AnimationDataTest::trackCustomResultType, - &AnimationDataTest::trackWrongIndex, &AnimationDataTest::trackWrongType, &AnimationDataTest::trackWrongResultType, + &AnimationDataTest::release, + &AnimationDataTest::debugAnimationTrackType, &AnimationDataTest::debugAnimationTrackTargetType}); } using namespace Math::Literals; +void AnimationDataTest::constructTrackData() { + AnimationTrackData trackData{ + AnimationTrackType::Vector3, + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + nullptr, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}}; + AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, {trackData}}}; + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + CORRADE_COMPARE(data.track(0).interpolation(), Animation::Interpolation::Linear); +} + +void AnimationDataTest::constructTrackDataResultType() { + AnimationTrackData trackData{ + AnimationTrackType::CubicHermite3D, + AnimationTrackType::Vector3, + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + nullptr, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}}; + AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, {trackData}}}; + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::CubicHermite3D); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + CORRADE_COMPARE(data.track(0).interpolation(), Animation::Interpolation::Linear); +} + +void AnimationDataTest::constructTrackDataTemplate() { + AnimationTrackData trackData{ + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + nullptr, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}}; + AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, {trackData}}}; + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::CubicHermite3D); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + CORRADE_COMPARE(data.track(0).interpolation(), Animation::Interpolation::Linear); +} + +void AnimationDataTest::constructTrackDataDefault() { + AnimationTrackData data; + /* no public accessors here, so nothing to check -- and such a track + shouldn't get added to AnimationData anyway */ + CORRADE_VERIFY(true); +} + void AnimationDataTest::construct() { - /* Ain't the prettiest, but trust me: you won't do it like this in the - plugins anyway */ struct Data { Float time; Vector3 position; @@ -89,25 +176,25 @@ void AnimationDataTest::construct() { view[2] = {7.5f, {1.0f, 0.3f, 2.1f}, Quaternion{}}; const int state = 5; - AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3, - AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].position, view.size(), sizeof(Data)}, - Animation::Interpolation::Constant, - animationInterpolatorFor(Animation::Interpolation::Constant)}}, - {AnimationTrackType::Quaternion, - AnimationTrackTargetType::Rotation3D, 1337, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].rotation, view.size(), sizeof(Data)}, - Animation::Interpolation::Linear, - animationInterpolatorFor(Animation::Interpolation::Linear)}} - }}, {-1.0f, 7.0f}, &state}; - + AnimationData data{std::move(buffer), { + AnimationTrackData{AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].position, view.size(), sizeof(Data)}, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}}, + AnimationTrackData{AnimationTrackTargetType::Rotation3D, 1337, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].rotation, view.size(), sizeof(Data)}, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}} + }, {-1.0f, 7.0f}, &state}; + + CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.duration(), (Range1D{-1.0f, 7.0f})); - CORRADE_COMPARE(data.data().size(), sizeof(Data)*3); + CORRADE_COMPARE(static_cast(data.data().data()), view.data()); + CORRADE_COMPARE(static_cast(data.mutableData().data()), view.data()); CORRADE_COMPARE(data.trackCount(), 2); CORRADE_COMPARE(data.importerState(), &state); @@ -117,28 +204,34 @@ void AnimationDataTest::construct() { CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); CORRADE_COMPARE(data.trackTarget(0), 42); - Animation::TrackView track = data.track(0); + Animation::TrackView track = data.track(0); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); CORRADE_COMPARE(track.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); + + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.keys().size(), 3); + CORRADE_COMPARE(mutableTrack.values().size(), 3); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); } { CORRADE_COMPARE(data.trackType(1), AnimationTrackType::Quaternion); CORRADE_COMPARE(data.trackResultType(1), AnimationTrackType::Quaternion); CORRADE_COMPARE(data.trackTargetType(1), AnimationTrackTargetType::Rotation3D); CORRADE_COMPARE(data.trackTarget(1), 1337); - Animation::TrackView track = data.track(1); + Animation::TrackView track = data.track(1); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); CORRADE_COMPARE(track.at(2.5f), Quaternion::rotation(32.5_degf, Vector3::yAxis())); + + /* Testing the mutable track just once is enough */ } } void AnimationDataTest::constructImplicitDuration() { - /* Ain't the prettiest, but trust me: you won't do it like this in the - plugins anyway */ struct Data { Float time; bool value; @@ -151,21 +244,20 @@ void AnimationDataTest::constructImplicitDuration() { view[3] = {7.0f, false}; const int state = 5; - AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Bool, - AnimationTrackTargetType(129), 0, - Animation::TrackView{ - {view, &view[0].time, 2, sizeof(Data)}, - {view, &view[0].value, 2, sizeof(Data)}, - Animation::Interpolation::Constant}}, - {AnimationTrackType::Bool, - AnimationTrackTargetType(130), 1, - Animation::TrackView{ - {view, &view[2].time, 2, sizeof(Data)}, - {view, &view[2].value, 2, sizeof(Data)}, - Animation::Interpolation::Linear}} - }}, &state}; - + AnimationData data{std::move(buffer), { + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{ + {view, &view[0].time, 2, sizeof(Data)}, + {view, &view[0].value, 2, sizeof(Data)}, + Animation::Interpolation::Constant}}, + AnimationTrackData{AnimationTrackTargetType(130), 1, + Animation::TrackView{ + {view, &view[2].time, 2, sizeof(Data)}, + {view, &view[2].value, 2, sizeof(Data)}, + Animation::Interpolation::Linear}} + }, &state}; + + CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.duration(), (Range1D{1.0f, 7.0f})); CORRADE_COMPARE(data.trackCount(), 2); CORRADE_COMPARE(data.importerState(), &state); @@ -175,24 +267,33 @@ void AnimationDataTest::constructImplicitDuration() { CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType(129)); CORRADE_COMPARE(data.trackTarget(0), 0); - Animation::TrackView track = data.track(0); + Animation::TrackView track = data.track(0); CORRADE_COMPARE(track.duration(), (Range1D{1.0f, 5.0f})); CORRADE_COMPARE(track.keys().size(), 2); CORRADE_COMPARE(track.values().size(), 2); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); CORRADE_COMPARE(track.at(6.0f), false); + + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(mutableTrack.keys().size(), 2); + CORRADE_COMPARE(mutableTrack.values().size(), 2); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(6.0f), false); } { CORRADE_COMPARE(data.trackType(1), AnimationTrackType::Bool); CORRADE_COMPARE(data.trackResultType(1), AnimationTrackType::Bool); CORRADE_COMPARE(data.trackTargetType(1), AnimationTrackTargetType(130)); CORRADE_COMPARE(data.trackTarget(1), 1); - Animation::TrackView track = data.track(1); + Animation::TrackView track = data.track(1); CORRADE_COMPARE(track.duration(), (Range1D{3.0f, 7.0f})); CORRADE_COMPARE(track.keys().size(), 2); CORRADE_COMPARE(track.values().size(), 2); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); CORRADE_COMPARE(track.at(4.5f), true); + + /* Testing the mutable track just once is enough */ } } @@ -201,6 +302,117 @@ void AnimationDataTest::constructImplicitDurationEmpty() { CORRADE_COMPARE(data.duration(), Range1D{}); } +void AnimationDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + std::pair keyframes[] { + {0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}} + }; + + const int state = 5; + AnimationData data{instanceData.dataFlags, keyframes, { + AnimationTrackData{AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + keyframes, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}} + }, {-1.0f, 7.0f}, &state}; + + CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.duration(), (Range1D{-1.0f, 7.0f})); + CORRADE_COMPARE(static_cast(data.data().data()), keyframes); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableData().data()), keyframes); + CORRADE_COMPARE(data.trackCount(), 1); + CORRADE_COMPARE(data.importerState(), &state); + + { + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + + Animation::TrackView track = data.track(0); + CORRADE_COMPARE(track.keys().size(), 2); + CORRADE_COMPARE(track.values().size(), 2); + CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(track.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); + + if(instanceData.dataFlags & DataFlag::Mutable) { + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.keys().size(), 2); + CORRADE_COMPARE(mutableTrack.values().size(), 2); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); + } + } +} + +void AnimationDataTest::constructImplicitDurationNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + std::pair keyframes[] { + {1.0f, true}, + {5.0f, false} + }; + + const int state = 5; + AnimationData data{instanceData.dataFlags, keyframes, { + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + }, &state}; + + CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(static_cast(data.data().data()), keyframes); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableData().data()), keyframes); + CORRADE_COMPARE(data.trackCount(), 1); + CORRADE_COMPARE(data.importerState(), &state); + + { + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::Bool); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Bool); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType(129)); + CORRADE_COMPARE(data.trackTarget(0), 0); + + Animation::TrackView track = data.track(0); + CORRADE_COMPARE(track.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(track.keys().size(), 2); + CORRADE_COMPARE(track.values().size(), 2); + CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(track.at(3.0f), true); + + if(instanceData.dataFlags & DataFlag::Mutable) { + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(mutableTrack.keys().size(), 2); + CORRADE_COMPARE(mutableTrack.values().size(), 2); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(3.0f), true); + } + } +} + +void AnimationDataTest::constructNotOwnedFlagOwned() { + std::ostringstream out; + Error redirectError{&out}; + AnimationData data{DataFlag::Owned, nullptr, {}, {-1.0f, 7.0f}}; + CORRADE_COMPARE(out.str(), + "Trade::AnimationData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void AnimationDataTest::constructImplicitDurationNotOwnedFlagOwned() { + std::ostringstream out; + Error redirectError{&out}; + AnimationData data{DataFlag::Owned, nullptr, {}}; + CORRADE_COMPARE(out.str(), + "Trade::AnimationData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + void AnimationDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); @@ -220,22 +432,20 @@ void AnimationDataTest::constructMove() { view[2] = {7.5f, {1.0f, 0.3f, 2.1f}, Quaternion{}}; const int state = 5; - AnimationData a{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3, - AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].position, view.size(), sizeof(Data)}, - Animation::Interpolation::Constant, - animationInterpolatorFor(Animation::Interpolation::Constant)}}, - {AnimationTrackType::Quaternion, - AnimationTrackTargetType::Rotation3D, 1337, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].rotation, view.size(), sizeof(Data)}, - Animation::Interpolation::Linear, - animationInterpolatorFor(Animation::Interpolation::Linear)}} - }}, {-1.0f, 7.0f}, &state}; + AnimationData a{std::move(buffer), { + AnimationTrackData{AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].position, view.size(), sizeof(Data)}, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}}, + AnimationTrackData{AnimationTrackTargetType::Rotation3D, 1337, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].rotation, view.size(), sizeof(Data)}, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}} + }, {-1.0f, 7.0f}, &state}; AnimationData b{std::move(a)}; @@ -250,7 +460,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(b.trackTargetType(0), AnimationTrackTargetType::Translation3D); CORRADE_COMPARE(b.trackTarget(0), 42); - Animation::TrackView track = b.track(0); + Animation::TrackView track = b.track(0); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); @@ -261,7 +471,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(b.trackTargetType(1), AnimationTrackTargetType::Rotation3D); CORRADE_COMPARE(b.trackTarget(1), 1337); - Animation::TrackView track = b.track(1); + Animation::TrackView track = b.track(1); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); @@ -283,7 +493,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(c.trackTargetType(0), AnimationTrackTargetType::Translation3D); CORRADE_COMPARE(c.trackTarget(0), 42); - Animation::TrackView track = c.track(0); + Animation::TrackView track = c.track(0); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); @@ -294,7 +504,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(c.trackTargetType(1), AnimationTrackTargetType::Rotation3D); CORRADE_COMPARE(c.trackTarget(1), 1337); - Animation::TrackView track = c.track(1); + Animation::TrackView track = c.track(1); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); @@ -305,9 +515,27 @@ void AnimationDataTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } -void AnimationDataTest::constructTrackDataDefault() { - AnimationTrackData data; - CORRADE_VERIFY(true); /* no public accessors here, so nothing to check */ +void AnimationDataTest::mutableAccessNotAllowed() { + const std::pair keyframes[] { + {1.0f, true}, + {5.0f, false} + }; + + AnimationData data{{}, keyframes, { + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + }}; + CORRADE_COMPARE(data.dataFlags(), DataFlags{}); + + std::ostringstream out; + Error redirectError{&out}; + data.mutableData(); + data.mutableTrack(0); + data.mutableTrack(0); + CORRADE_COMPARE(out.str(), + "Trade::AnimationData::mutableData(): the animation is not mutable\n" + "Trade::AnimationData::mutableTrack(): the animation is not mutable\n" + "Trade::AnimationData::mutableTrack(): the animation is not mutable\n"); } void AnimationDataTest::trackCustomResultType() { @@ -322,17 +550,15 @@ void AnimationDataTest::trackCustomResultType() { view[0] = {0.0f, {300, 100, 10}}; view[1] = {5.0f, {30, 60, 100}}; - AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3i, - AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].position, view.size(), sizeof(Data)}, - [](const Vector3i& a, const Vector3i& b, Float t) -> Vector3 { - return Math::lerp(Vector3{a}*0.01f, Vector3{b}*0.01f, t); - }}}} - }}; + AnimationData data{std::move(buffer), { + AnimationTrackData{AnimationTrackTargetType::Scaling3D, 0, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].position, view.size(), sizeof(Data)}, + [](const Vector3i& a, const Vector3i& b, Float t) -> Vector3 { + return Math::lerp(Vector3{a}*0.01f, Vector3{b}*0.01f, t); + }}} + }}; CORRADE_COMPARE((data.track(0).at(2.5f)), (Vector3{1.65f, 0.8f, 0.55f})); } @@ -360,11 +586,11 @@ void AnimationDataTest::trackWrongType() { std::ostringstream out; Error redirectError{&out}; - AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3i, - AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, {}} - }}}; + AnimationData data{nullptr, { + AnimationTrackData{AnimationTrackType::Vector3i, + AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} + }}; data.track(0); @@ -375,17 +601,35 @@ void AnimationDataTest::trackWrongResultType() { std::ostringstream out; Error redirectError{&out}; - AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3i, - AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, {}} - }}}; + AnimationData data{nullptr, { + AnimationTrackData{AnimationTrackType::Vector3i, + AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} + }}; data.track(0); CORRADE_COMPARE(out.str(), "Trade::AnimationData::track(): improper result type requested for Trade::AnimationTrackType::Vector3\n"); } +void AnimationDataTest::release() { + const std::pair keyframes[] { + {1.0f, true}, + {5.0f, false} + }; + + AnimationData data{{}, keyframes, { + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + }}; + CORRADE_COMPARE(data.trackCount(), 1); + + Containers::Array released = data.release(); + CORRADE_COMPARE(data.data(), nullptr); + CORRADE_COMPARE(data.trackCount(), 0); + CORRADE_COMPARE(static_cast(released.data()), keyframes); +} + void AnimationDataTest::debugAnimationTrackType() { std::ostringstream out; diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 1ed9a94545..f89f44e453 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -44,11 +44,11 @@ target_include_directories(TradeAbstractImporterTest PRIVATE ${CMAKE_CURRENT_BIN corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) +corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) -corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) +corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeObjectData3DTest ObjectData3DTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTrade) @@ -56,6 +56,7 @@ corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade) set_property(TARGET TradeAnimationDataTest + TradeMeshDataTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( @@ -66,10 +67,18 @@ set_target_properties( TradeImageDataTest TradeLightDataTest TradeMaterialDataTest - TradeMeshData2DTest - TradeMeshData3DTest TradeObjectData2DTest TradeObjectData3DTest TradeSceneDataTest TradeTextureDataTest PROPERTIES FOLDER "Magnum/Trade/Test") + +if(MAGNUM_BUILD_DEPRECATED) + corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) + corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) + + set_target_properties( + TradeMeshData2DTest + TradeMeshData3DTest + PROPERTIES FOLDER "Magnum/Trade/Test") +endif() diff --git a/src/Magnum/Trade/Test/DataTest.cpp b/src/Magnum/Trade/Test/DataTest.cpp new file mode 100644 index 0000000000..e1253d498b --- /dev/null +++ b/src/Magnum/Trade/Test/DataTest.cpp @@ -0,0 +1,62 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include +#include + +#include "Magnum/Trade/Data.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct DataTest: TestSuite::Tester { + explicit DataTest(); + + void debugDataFlag(); + void debugDataFlags(); +}; + +DataTest::DataTest() { + addTests({&DataTest::debugDataFlag, + &DataTest::debugDataFlags}); +} + +void DataTest::debugDataFlag() { + std::ostringstream out; + + Debug{&out} << DataFlag::Owned << DataFlag(0xf0); + CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned Trade::DataFlag(0xf0)\n"); +} + +void DataTest::debugDataFlags() { + std::ostringstream out; + + Debug{&out} << (DataFlag::Owned|DataFlag::Mutable) << DataFlags{}; + CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned|Trade::DataFlag::Mutable Trade::DataFlags{}\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::DataTest) diff --git a/src/Magnum/Trade/Test/ImageDataTest.cpp b/src/Magnum/Trade/Test/ImageDataTest.cpp index d68a895bca..bfe8207237 100644 --- a/src/Magnum/Trade/Test/ImageDataTest.cpp +++ b/src/Magnum/Trade/Test/ImageDataTest.cpp @@ -47,6 +47,15 @@ struct ImageDataTest: TestSuite::Tester { void constructCompressedGeneric(); void constructCompressedImplementationSpecific(); + void constructGenericNotOwned(); + void constructImplementationSpecificNotOwned(); + void constructCompressedGenericNotOwned(); + void constructCompressedImplementationSpecificNotOwned(); + void constructGenericNotOwnedFlagOwned(); + void constructImplementationSpecificNotOwnedFlagOwned(); + void constructCompressedGenericNotOwnedFlagOwned(); + void constructCompressedImplementationSpecificNotOwnedFlagOwned(); + void constructInvalidSize(); void constructCompressedInvalidSize(); @@ -67,6 +76,7 @@ struct ImageDataTest: TestSuite::Tester { void data(); void dataRvalue(); + void mutableAccessNotAllowed(); void dataProperties(); @@ -91,11 +101,30 @@ template<> struct MutabilityTraits { static const char* name() { return "MutableImageView"; } }; +struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable}, +}; + ImageDataTest::ImageDataTest() { addTests({&ImageDataTest::constructGeneric, &ImageDataTest::constructImplementationSpecific, &ImageDataTest::constructCompressedGeneric, - &ImageDataTest::constructCompressedImplementationSpecific, + &ImageDataTest::constructCompressedImplementationSpecific}); + + addInstancedTests({&ImageDataTest::constructGenericNotOwned, + &ImageDataTest::constructImplementationSpecificNotOwned, + &ImageDataTest::constructCompressedGenericNotOwned, + &ImageDataTest::constructCompressedImplementationSpecificNotOwned}, + Containers::arraySize(NotOwnedData)); + + addTests({&ImageDataTest::constructGenericNotOwnedFlagOwned, + &ImageDataTest::constructImplementationSpecificNotOwnedFlagOwned, + &ImageDataTest::constructCompressedGenericNotOwnedFlagOwned, + &ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned, &ImageDataTest::constructInvalidSize, &ImageDataTest::constructCompressedInvalidSize, @@ -121,6 +150,7 @@ ImageDataTest::ImageDataTest() { &ImageDataTest::data, &ImageDataTest::dataRvalue, + &ImageDataTest::mutableAccessNotAllowed, &ImageDataTest::dataProperties, @@ -167,14 +197,19 @@ void ImageDataTest::constructGeneric() { int state; ImageData2D a{PixelFormat::RGBA8Unorm, {1, 3}, Containers::Array{data, 4*4}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 4); CORRADE_COMPARE(a.format(), PixelFormat::RGBA8Unorm); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 4); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } { auto data = new char[3*2]; @@ -182,14 +217,19 @@ void ImageDataTest::constructGeneric() { ImageData2D a{PixelStorage{}.setAlignment(1), PixelFormat::R16UI, {1, 3}, Containers::Array{data, 3*2}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), PixelFormat::R16UI); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 2); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } } @@ -202,14 +242,19 @@ void ImageDataTest::constructImplementationSpecific() { ImageData2D a{PixelStorage{}.setAlignment(1), Vk::PixelFormat::R32G32B32F, {1, 3}, Containers::Array{data, 3*12}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), pixelFormatWrap(Vk::PixelFormat::R32G32B32F)); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 12); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } @@ -220,13 +265,18 @@ void ImageDataTest::constructImplementationSpecific() { ImageData2D a{PixelStorage{}.setAlignment(1), GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, Containers::Array{data, 3*6}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); CORRADE_COMPARE(a.pixelSize(), 6); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } @@ -236,14 +286,19 @@ void ImageDataTest::constructImplementationSpecific() { int state; ImageData2D a{PixelStorage{}.setAlignment(1), 666, 1337, 6, {1, 3}, Containers::Array{data, 3*6}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); CORRADE_COMPARE(a.pixelSize(), 6); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } } @@ -255,12 +310,15 @@ void ImageDataTest::constructCompressedGeneric() { ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } { auto data = new char[8]; @@ -269,12 +327,15 @@ void ImageDataTest::constructCompressedGeneric() { CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } } @@ -288,18 +349,270 @@ void ImageDataTest::constructCompressedImplementationSpecific() { GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } /* Manual properties not implemented yet */ } +void ImageDataTest::constructGenericNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + { + char data[4*4]; + int state; + ImageData2D a{PixelFormat::RGBA8Unorm, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 4); + CORRADE_COMPARE(a.format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 4); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } { + char data[3*2]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + PixelFormat::R16UI, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), PixelFormat::R16UI); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 2); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructImplementationSpecificNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Single format */ + { + char data[3*12]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + Vk::PixelFormat::R32G32B32F, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), pixelFormatWrap(Vk::PixelFormat::R32G32B32F)); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Format + extra */ + { + char data[3*6]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); + CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); + CORRADE_COMPARE(a.pixelSize(), 6); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Manual pixel size */ + { + char data[3*6]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), 666, 1337, 6, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); + CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); + CORRADE_COMPARE(a.pixelSize(), 6); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructCompressedGenericNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + { + char data[8]; + int state; + ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); + CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } { + char data[8]; + int state; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); + CORRADE_COMPARE(a.size(), Vector2i(4, 4)); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructCompressedImplementationSpecificNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Format with autodetection */ + { + char data[8]; + int state; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); + CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Manual properties not implemented yet */ +} + +void ImageDataTest::constructGenericNotOwnedFlagOwned() { + char data[4*4]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlag::Owned, data}; + ImageData2D{PixelStorage{}.setAlignment(1), PixelFormat::R16UI, {1, 3}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructImplementationSpecificNotOwnedFlagOwned() { + char data[3*12]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{PixelStorage{}.setAlignment(1), Vk::PixelFormat::R32G32B32F, {1, 3}, DataFlag::Owned, data}; + ImageData2D{PixelStorage{}.setAlignment(1), GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructCompressedGenericNotOwnedFlagOwned() { + char data[8]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + ImageData2D{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned() { + char data[8]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + void ImageDataTest::constructInvalidSize() { std::ostringstream out; Error redirectError{&out}; @@ -343,6 +656,7 @@ void ImageDataTest::constructMoveGeneric() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), PixelFormat::RGBA32F); @@ -360,6 +674,7 @@ void ImageDataTest::constructMoveGeneric() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{2, 6})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!c.isCompressed()); CORRADE_COMPARE(c.storage().alignment(), 1); CORRADE_COMPARE(c.format(), PixelFormat::RGBA32F); @@ -384,6 +699,7 @@ void ImageDataTest::constructMoveImplementationSpecific() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -402,6 +718,7 @@ void ImageDataTest::constructMoveImplementationSpecific() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), Vector2i(2, 6)); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!c.isCompressed()); CORRADE_COMPARE(c.storage().alignment(), 1); CORRADE_COMPARE(c.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -424,6 +741,7 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); @@ -439,6 +757,7 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(c.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); @@ -459,6 +778,7 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -474,6 +794,7 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(c.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -493,6 +814,7 @@ void ImageDataTest::constructMoveAttachState() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -515,6 +837,7 @@ void ImageDataTest::constructMoveCompressedAttachState() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -603,6 +926,31 @@ void ImageDataTest::dataRvalue() { CORRADE_COMPARE(released.data(), data); } +void ImageDataTest::mutableAccessNotAllowed() { + const char data[4*4]{}; + ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, data}; + + std::ostringstream out; + Error redirectError{&out}; + a.mutableData(); + a.mutablePixels(); + /* Can't do just MutableImageView2D(a) because the compiler then treats it + as a function. Can't do MutableImageView2D{a} because that doesn't work + on old Clang. So it's this mess, then. Sigh. */ + auto b = MutableImageView2D(a); + static_cast(b); + auto c = MutableCompressedImageView2D(a); + static_cast(c); + /* a.mutablePixels() calls non-templated mutablePixels(), so assume + there it will blow up correctly as well (can't test because it asserts + inside arrayCast() due to zero stride) */ + CORRADE_COMPARE(out.str(), + "Trade::ImageData::mutableData(): the image is not mutable\n" + "Trade::ImageData::mutablePixels(): the image is not mutable\n" + "Trade::ImageData: the image is not mutable\n" + "Trade::ImageData: the image is not mutable\n"); +} + void ImageDataTest::dataProperties() { ImageData3D image{ PixelStorage{} @@ -646,12 +994,12 @@ void ImageDataTest::pixels1D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView1D pixels = image.pixels(); + Containers::StridedArrayView1D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), 2); CORRADE_COMPARE(pixels.stride(), 3); CORRADE_COMPARE(pixels.data(), image.data() + 3*3); } { - Containers::StridedArrayView1D pixels = Containers::arrayCast<1, const Color3ub>(cimage.pixels()); + Containers::StridedArrayView1D pixels = cimage.pixels(); CORRADE_COMPARE(pixels.size(), 2); CORRADE_COMPARE(pixels.stride(), 3); CORRADE_COMPARE(pixels.data(), cimage.data() + 3*3); @@ -671,12 +1019,12 @@ void ImageDataTest::pixels2D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView2D pixels = image.pixels(); + Containers::StridedArrayView2D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D::Size{4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D::Stride{20, 3})); CORRADE_COMPARE(pixels.data(), image.data() + 2*20 + 3*3); } { - Containers::StridedArrayView2D pixels = Containers::arrayCast<2, const Color3ub>(cimage.pixels()); + Containers::StridedArrayView2D pixels = cimage.pixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D::Size{4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D::Stride{20, 3})); CORRADE_COMPARE(pixels.data(), cimage.data() + 2*20 + 3*3); @@ -697,7 +1045,7 @@ void ImageDataTest::pixels3D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView3D pixels = image.pixels(); + Containers::StridedArrayView3D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D::Size{3, 4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D::Stride{140, 20, 3})); CORRADE_COMPARE(pixels.data(), image.data() + 140 + 2*20 + 3*3); diff --git a/src/Magnum/Trade/Test/MeshData2DTest.cpp b/src/Magnum/Trade/Test/MeshData2DTest.cpp index 315e8db643..fed1d16865 100644 --- a/src/Magnum/Trade/Test/MeshData2DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData2DTest.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA + #include #include "Magnum/Mesh.h" @@ -42,59 +45,115 @@ struct MeshData2DTest: TestSuite::Tester { void constructMove(); }; +CORRADE_IGNORE_DEPRECATED_PUSH + +using namespace Math::Literals; + +const UnsignedByte Indices[]{12, 1, 0}; +const struct Vertex { + Vector2 position1, position2; + Vector2 textureCoords1, textureCoords2, textureCoords3; + Color4 color; +} Vertices[] { + {{0.5f, 1.0f}, {1.4f, 0.2f}, + {0.0f, 0.0f}, {0.1f, 0.2f}, {0.0f, 0.0f}, + 0xff98ab_rgbf}, + {{-1.0f, 0.3f}, {1.1f, 0.13f}, + {0.3f, 0.7f}, {0.7f, 1.0f}, {1.0f, 1.0f}, + 0xff3366_rgbf} +}; +const int State = 3; + +struct { + const char* name; + const MeshData2D data, dataNonIndexed; +} ConstructData[] { + {"", + MeshData2D{MeshPrimitive::Lines, {12, 1, 0}, + {{{0.5f, 1.0f}, {-1.0f, 0.3f}}, + {{1.4f, 0.2f}, {1.1f, 0.13f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}, + {{0.1f, 0.2f}, {0.7f, 1.0f}}, + {{0.0f, 0.0f}, {1.0f, 1.0f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}, + MeshData2D{MeshPrimitive::Lines, {}, + {{{0.5f, 1.0f}, {-1.0f, 0.3f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}}, + {"from MeshData", + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData2D{MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, MeshData::ImplicitVertexCount, &State}}, + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData2D{MeshData{MeshPrimitive::Lines, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, MeshData::ImplicitVertexCount, &State}} + } +}; + MeshData2DTest::MeshData2DTest() { - addTests({&MeshData2DTest::construct, - &MeshData2DTest::constructNonIndexed, - &MeshData2DTest::constructNoTexCoords, + addInstancedTests({&MeshData2DTest::construct, + &MeshData2DTest::constructNonIndexed}, + Containers::arraySize(ConstructData)); + + addTests({&MeshData2DTest::constructNoTexCoords, &MeshData2DTest::constructNoColors, &MeshData2DTest::constructCopy, &MeshData2DTest::constructMove}); } -using namespace Math::Literals; - void MeshData2DTest::construct() { - const int a{}; - const MeshData2D data{MeshPrimitive::Lines, {12, 1, 0}, - {{{0.5f, 1.0f}, {-1.0f, 0.3f}}, - {{1.4f, 0.2f}, {1.1f, 0.13f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}, - {{0.1f, 0.2f}, {0.7f, 1.0f}}, - {{0.0f, 0.0f}, {1.0f, 1.0f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_COMPARE(data.primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(data.data.primitive(), MeshPrimitive::Lines); - CORRADE_VERIFY(data.isIndexed()); - CORRADE_COMPARE(data.indices(), (std::vector{12, 1, 0})); + CORRADE_VERIFY(data.data.isIndexed()); + CORRADE_COMPARE(data.data.indices(), (std::vector{12, 1, 0})); - CORRADE_COMPARE(data.positionArrayCount(), 2); - CORRADE_COMPARE(data.positions(0), (std::vector{{0.5f, 1.0f}, {-1.0f, 0.3f}})); - CORRADE_COMPARE(data.positions(1), (std::vector{{1.4f, 0.2f}, {1.1f, 0.13f}})); + CORRADE_COMPARE(data.data.positionArrayCount(), 2); + CORRADE_COMPARE(data.data.positions(0), (std::vector{{0.5f, 1.0f}, {-1.0f, 0.3f}})); + CORRADE_COMPARE(data.data.positions(1), (std::vector{{1.4f, 0.2f}, {1.1f, 0.13f}})); - CORRADE_VERIFY(data.hasTextureCoords2D()); - CORRADE_COMPARE(data.textureCoords2DArrayCount(), 3); - CORRADE_COMPARE(data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); - CORRADE_COMPARE(data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); - CORRADE_COMPARE(data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); + CORRADE_VERIFY(data.data.hasTextureCoords2D()); + CORRADE_COMPARE(data.data.textureCoords2DArrayCount(), 3); + CORRADE_COMPARE(data.data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); + CORRADE_COMPARE(data.data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); + CORRADE_COMPARE(data.data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); - CORRADE_VERIFY(data.hasColors()); - CORRADE_COMPARE(data.colorArrayCount(), 1); - CORRADE_COMPARE(data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); + CORRADE_VERIFY(data.data.hasColors()); + CORRADE_COMPARE(data.data.colorArrayCount(), 1); + CORRADE_COMPARE(data.data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); - CORRADE_COMPARE(data.importerState(), &a); + CORRADE_COMPARE(data.data.importerState(), &State); } void MeshData2DTest::constructNonIndexed() { - const int a{}; - const MeshData2D data{MeshPrimitive::Lines, {}, - {{{0.5f, 1.0f}, {-1.0f, 0.3f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_VERIFY(!data.isIndexed()); + CORRADE_VERIFY(!data.dataNonIndexed.isIndexed()); } void MeshData2DTest::constructNoTexCoords() { @@ -168,6 +227,8 @@ void MeshData2DTest::constructMove() { CORRADE_COMPARE(d.importerState(), &a); } +CORRADE_IGNORE_DEPRECATED_POP + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshData2DTest) diff --git a/src/Magnum/Trade/Test/MeshData3DTest.cpp b/src/Magnum/Trade/Test/MeshData3DTest.cpp index 937e3ccd17..250130ca62 100644 --- a/src/Magnum/Trade/Test/MeshData3DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData3DTest.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA + #include #include "Magnum/Mesh.h" @@ -43,66 +46,129 @@ struct MeshData3DTest: TestSuite::Tester { void constructMove(); }; +CORRADE_IGNORE_DEPRECATED_PUSH + +using namespace Math::Literals; + +const UnsignedByte Indices[]{12, 1, 0}; +const struct Vertex { + Vector3 position1, position2; + Vector3 normal; + Vector2 textureCoords1, textureCoords2, textureCoords3; + Color4 color; +} Vertices[] { + {{0.5f, 1.0f, 0.1f}, {1.4f, 0.2f, 0.5f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f}, {0.1f, 0.2f}, {0.0f, 0.0f}, + 0xff98ab_rgbf}, + {{-1.0f, 0.3f, -1.0f}, {1.1f, 0.13f, -0.3f}, + {-1.0f, 0.0f, 0.0f}, + {0.3f, 0.7f}, {0.7f, 1.0f}, {1.0f, 1.0f}, + 0xff3366_rgbf} +}; +const int State = 3; + +struct { + const char* name; + const MeshData3D data, dataNonIndexed; +} ConstructData[] { + {"", + MeshData3D{MeshPrimitive::Lines, {12, 1, 0}, + {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}, + {{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}}}, + {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}, + {{0.1f, 0.2f}, {0.7f, 1.0f}}, + {{0.0f, 0.0f}, {1.0f, 1.0f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}, + MeshData3D{MeshPrimitive::Lines, {}, + {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}}, + {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}}, + {"from MeshData", + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData3D{MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Normal, + Containers::StridedArrayView1D{Vertices, &Vertices[0].normal, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, MeshData::ImplicitVertexCount, &State}}, + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData3D{MeshData{MeshPrimitive::Lines, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Normal, + Containers::StridedArrayView1D{Vertices, &Vertices[0].normal, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, MeshData::ImplicitVertexCount, &State}} + } +}; + MeshData3DTest::MeshData3DTest() { - addTests({&MeshData3DTest::construct, - &MeshData3DTest::constructNonIndexed, - &MeshData3DTest::constructNoNormals, + addInstancedTests({&MeshData3DTest::construct, + &MeshData3DTest::constructNonIndexed}, + Containers::arraySize(ConstructData)); + + addTests({&MeshData3DTest::constructNoNormals, &MeshData3DTest::constructNoTexCoords, &MeshData3DTest::constructNoColors, &MeshData3DTest::constructCopy, &MeshData3DTest::constructMove}); } -using namespace Math::Literals; - void MeshData3DTest::construct() { - const int a{}; - const MeshData3D data{MeshPrimitive::Lines, {12, 1, 0}, - {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}, - {{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}}}, - {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}, - {{0.1f, 0.2f}, {0.7f, 1.0f}}, - {{0.0f, 0.0f}, {1.0f, 1.0f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_COMPARE(data.primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(data.data.primitive(), MeshPrimitive::Lines); - CORRADE_VERIFY(data.isIndexed()); - CORRADE_COMPARE(data.indices(), (std::vector{12, 1, 0})); + CORRADE_VERIFY(data.data.isIndexed()); + CORRADE_COMPARE(data.data.indices(), (std::vector{12, 1, 0})); - CORRADE_COMPARE(data.positionArrayCount(), 2); - CORRADE_COMPARE(data.positions(0), (std::vector{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}})); - CORRADE_COMPARE(data.positions(1), (std::vector{{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}})); + CORRADE_COMPARE(data.data.positionArrayCount(), 2); + CORRADE_COMPARE(data.data.positions(0), (std::vector{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}})); + CORRADE_COMPARE(data.data.positions(1), (std::vector{{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}})); - CORRADE_VERIFY(data.hasNormals()); - CORRADE_COMPARE(data.normalArrayCount(), 1); - CORRADE_COMPARE(data.normals(0), (std::vector{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}})); + CORRADE_VERIFY(data.data.hasNormals()); + CORRADE_COMPARE(data.data.normalArrayCount(), 1); + CORRADE_COMPARE(data.data.normals(0), (std::vector{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}})); - CORRADE_VERIFY(data.hasTextureCoords2D()); - CORRADE_COMPARE(data.textureCoords2DArrayCount(), 3); - CORRADE_COMPARE(data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); - CORRADE_COMPARE(data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); - CORRADE_COMPARE(data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); + CORRADE_VERIFY(data.data.hasTextureCoords2D()); + CORRADE_COMPARE(data.data.textureCoords2DArrayCount(), 3); + CORRADE_COMPARE(data.data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); + CORRADE_COMPARE(data.data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); + CORRADE_COMPARE(data.data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); - CORRADE_VERIFY(data.hasColors()); - CORRADE_COMPARE(data.colorArrayCount(), 1); - CORRADE_COMPARE(data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); + CORRADE_VERIFY(data.data.hasColors()); + CORRADE_COMPARE(data.data.colorArrayCount(), 1); + CORRADE_COMPARE(data.data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); - CORRADE_COMPARE(data.importerState(), &a); + CORRADE_COMPARE(data.data.importerState(), &State); } void MeshData3DTest::constructNonIndexed() { - const int a{}; - const MeshData3D data{MeshPrimitive::Lines, {}, - {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}}, - {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_VERIFY(!data.isIndexed()); + CORRADE_VERIFY(!data.dataNonIndexed.isIndexed()); } void MeshData3DTest::constructNoNormals() { @@ -195,6 +261,8 @@ void MeshData3DTest::constructMove() { CORRADE_COMPARE(d.importerState(), &a); } +CORRADE_IGNORE_DEPRECATED_POP + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshData3DTest) diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp new file mode 100644 index 0000000000..628e20da33 --- /dev/null +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -0,0 +1,2544 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include +#include +#include +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Half.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct MeshDataTest: TestSuite::Tester { + explicit MeshDataTest(); + + void customAttributeName(); + void customAttributeNameTooLarge(); + void customAttributeNameNotCustom(); + void debugAttributeName(); + + void constructIndex(); + void constructIndexTypeErased(); + void constructIndexTypeErasedWrongSize(); + void constructIndex2D(); + void constructIndex2DWrongSize(); + void constructIndex2DNonContiguous(); + void constructIndexNullptr(); + + void constructAttribute(); + void constructAttributeCustom(); + void constructAttribute2D(); + void constructAttribute2DWrongSize(); + void constructAttribute2DNonContiguous(); + void constructAttributeTypeErased(); + void constructAttributeNullptr(); + void constructAttributePadding(); + void constructAttributeNonOwningArray(); + void constructAttributeOffsetOnly(); + void constructAttributeImplementationSpecificFormat(); + void constructAttributeWrongFormat(); + void constructAttributeWrongStride(); + void constructAttributeWrongDataAccess(); + + void constructArrayAttribute(); + void constructArrayAttributeNonContiguous(); + void constructArrayAttribute2D(); + void constructArrayAttribute2DWrongSize(); + void constructArrayAttribute2DNonContiguous(); + void constructArrayAttributeTypeErased(); + void constructArrayAttributeOffsetOnly(); + void constructArrayAttributeNotAllowed(); + + void construct(); + + void constructZeroIndices(); + void constructZeroAttributes(); + void constructZeroVertices(); + void constructIndexless(); + void constructIndexlessZeroVertices(); + void constructAttributeless(); + void constructIndexlessAttributeless(); + void constructIndexlessAttributelessZeroVertices(); + + void constructNotOwned(); + void constructIndicesNotOwned(); + void constructVerticesNotOwned(); + void constructIndexlessNotOwned(); + void constructAttributelessNotOwned(); + + void constructIndexDataButNotIndexed(); + void constructAttributelessImplicitVertexCount(); + void constructIndicesNotContained(); + void constructAttributeNotContained(); + void constructInconsitentVertexCount(); + void constructNotOwnedIndexFlagOwned(); + void constructNotOwnedVertexFlagOwned(); + void constructIndicesNotOwnedFlagOwned(); + void constructVerticesNotOwnedFlagOwned(); + void constructIndexlessNotOwnedFlagOwned(); + void constructAttributelessNotOwnedFlagOwned(); + void constructInvalidAttributeData(); + + void constructCopy(); + void constructMove(); + + template void indicesAsArray(); + void indicesIntoArrayInvalidSize(); + template void positions2DAsArray(); + template void positions2DAsArrayPackedUnsigned(); + template void positions2DAsArrayPackedSigned(); + template void positions2DAsArrayPackedUnsignedNormalized(); + template void positions2DAsArrayPackedSignedNormalized(); + void positions2DIntoArrayInvalidSize(); + template void positions3DAsArray(); + template void positions3DAsArrayPackedUnsigned(); + template void positions3DAsArrayPackedSigned(); + template void positions3DAsArrayPackedUnsignedNormalized(); + template void positions3DAsArrayPackedSignedNormalized(); + void positions3DIntoArrayInvalidSize(); + template void normalsAsArray(); + template void normalsAsArrayPackedSignedNormalized(); + void normalsIntoArrayInvalidSize(); + template void textureCoordinates2DAsArray(); + template void textureCoordinates2DAsArrayPackedUnsigned(); + template void textureCoordinates2DAsArrayPackedSigned(); + template void textureCoordinates2DAsArrayPackedUnsignedNormalized(); + template void textureCoordinates2DAsArrayPackedSignedNormalized(); + void textureCoordinates2DIntoArrayInvalidSize(); + template void colorsAsArray(); + template void colorsAsArrayPackedUnsignedNormalized(); + void colorsIntoArrayInvalidSize(); + + void implementationSpecificVertexFormat(); + void implementationSpecificVertexFormatWrongAccess(); + void implementationSpecificVertexFormatNotContained(); + + void arrayAttribute(); + void arrayAttributeWrongAccess(); + + void mutableAccessNotAllowed(); + + void indicesNotIndexed(); + void indicesWrongType(); + + void attributeNotFound(); + void attributeWrongType(); + + void releaseIndexData(); + void releaseAttributeData(); + void releaseVertexData(); +}; + +struct { + const char* name; + UnsignedInt vertexCount, expectedVertexCount; +} ConstructData[] { + {"implicit vertex count", MeshData::ImplicitVertexCount, 3}, + {"explicit vertex count", 3, 3}, + {"explicit large vertex count", 17, 17}, + {"explicit zero vertex count", 0, 0} +}; + +struct { + const char* name; + DataFlags indexDataFlags, vertexDataFlags; +} NotOwnedData[] { + {"", {}, {}}, + {"indices mutable", DataFlag::Mutable, {}}, + {"vertices mutable", {}, DataFlag::Mutable}, + {"both mutable", DataFlag::Mutable, DataFlag::Mutable} +}; + +struct { + const char* name; + DataFlags dataFlags; +} SingleNotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable} +}; + +MeshDataTest::MeshDataTest() { + addTests({&MeshDataTest::customAttributeName, + &MeshDataTest::customAttributeNameTooLarge, + &MeshDataTest::customAttributeNameNotCustom, + &MeshDataTest::debugAttributeName, + + &MeshDataTest::constructIndex, + &MeshDataTest::constructIndexTypeErased, + &MeshDataTest::constructIndexTypeErasedWrongSize, + &MeshDataTest::constructIndex2D, + &MeshDataTest::constructIndex2DWrongSize, + &MeshDataTest::constructIndex2DNonContiguous, + &MeshDataTest::constructIndexNullptr, + + &MeshDataTest::constructAttribute, + &MeshDataTest::constructAttributeCustom, + &MeshDataTest::constructAttribute2D, + &MeshDataTest::constructAttribute2DWrongSize, + &MeshDataTest::constructAttribute2DNonContiguous, + &MeshDataTest::constructAttributeTypeErased, + &MeshDataTest::constructAttributeNullptr, + &MeshDataTest::constructAttributePadding, + &MeshDataTest::constructAttributeNonOwningArray, + &MeshDataTest::constructAttributeOffsetOnly, + &MeshDataTest::constructAttributeImplementationSpecificFormat, + &MeshDataTest::constructAttributeWrongFormat, + &MeshDataTest::constructAttributeWrongStride, + &MeshDataTest::constructAttributeWrongDataAccess, + + &MeshDataTest::constructArrayAttribute, + &MeshDataTest::constructArrayAttributeNonContiguous, + &MeshDataTest::constructArrayAttribute2D, + &MeshDataTest::constructArrayAttribute2DWrongSize, + &MeshDataTest::constructArrayAttribute2DNonContiguous, + &MeshDataTest::constructArrayAttributeTypeErased, + &MeshDataTest::constructArrayAttributeOffsetOnly, + &MeshDataTest::constructArrayAttributeNotAllowed}); + + addInstancedTests({&MeshDataTest::construct}, + Containers::arraySize(ConstructData)); + + addTests({&MeshDataTest::constructZeroIndices, + &MeshDataTest::constructZeroAttributes, + &MeshDataTest::constructZeroVertices, + &MeshDataTest::constructIndexless, + &MeshDataTest::constructIndexlessZeroVertices, + &MeshDataTest::constructAttributeless, + &MeshDataTest::constructIndexlessAttributeless, + &MeshDataTest::constructIndexlessAttributelessZeroVertices}); + + addInstancedTests({&MeshDataTest::constructNotOwned}, + Containers::arraySize(NotOwnedData)); + addInstancedTests({&MeshDataTest::constructIndicesNotOwned, + &MeshDataTest::constructVerticesNotOwned, + &MeshDataTest::constructIndexlessNotOwned, + &MeshDataTest::constructAttributelessNotOwned}, + Containers::arraySize(SingleNotOwnedData)); + + addTests({&MeshDataTest::constructIndexDataButNotIndexed, + &MeshDataTest::constructAttributelessImplicitVertexCount, + &MeshDataTest::constructIndicesNotContained, + &MeshDataTest::constructAttributeNotContained, + &MeshDataTest::constructInconsitentVertexCount, + &MeshDataTest::constructNotOwnedIndexFlagOwned, + &MeshDataTest::constructNotOwnedVertexFlagOwned, + &MeshDataTest::constructIndicesNotOwnedFlagOwned, + &MeshDataTest::constructVerticesNotOwnedFlagOwned, + &MeshDataTest::constructIndexlessNotOwnedFlagOwned, + &MeshDataTest::constructAttributelessNotOwnedFlagOwned, + &MeshDataTest::constructInvalidAttributeData, + + &MeshDataTest::constructCopy, + &MeshDataTest::constructMove, + + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesIntoArrayInvalidSize, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DIntoArrayInvalidSize, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DIntoArrayInvalidSize, + &MeshDataTest::normalsAsArray, + &MeshDataTest::normalsAsArray, + &MeshDataTest::normalsAsArrayPackedSignedNormalized, + &MeshDataTest::normalsAsArrayPackedSignedNormalized, + &MeshDataTest::normalsIntoArrayInvalidSize, + &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedSigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedSigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized, + &MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized, + &MeshDataTest::textureCoordinates2DIntoArrayInvalidSize, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsIntoArrayInvalidSize, + + &MeshDataTest::implementationSpecificVertexFormat, + &MeshDataTest::implementationSpecificVertexFormatWrongAccess, + &MeshDataTest::implementationSpecificVertexFormatNotContained, + + &MeshDataTest::arrayAttribute, + &MeshDataTest::arrayAttributeWrongAccess, + + &MeshDataTest::mutableAccessNotAllowed, + + &MeshDataTest::indicesNotIndexed, + &MeshDataTest::indicesWrongType, + + &MeshDataTest::attributeNotFound, + &MeshDataTest::attributeWrongType, + + &MeshDataTest::releaseIndexData, + &MeshDataTest::releaseAttributeData, + &MeshDataTest::releaseVertexData}); +} + +void MeshDataTest::customAttributeName() { + CORRADE_VERIFY(!isMeshAttributeCustom(MeshAttribute::Position)); + CORRADE_VERIFY(!isMeshAttributeCustom(MeshAttribute(32767))); + CORRADE_VERIFY(isMeshAttributeCustom(MeshAttribute::Custom)); + CORRADE_VERIFY(isMeshAttributeCustom(MeshAttribute(65535))); + + CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(0)), 32768); + CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(8290)), 41058); + CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(32767)), 65535); + + CORRADE_COMPARE(meshAttributeCustom(MeshAttribute::Custom), 0); + CORRADE_COMPARE(meshAttributeCustom(MeshAttribute(41058)), 8290); + CORRADE_COMPARE(meshAttributeCustom(MeshAttribute(65535)), 32767); + + constexpr bool is = isMeshAttributeCustom(MeshAttribute(41058)); + CORRADE_VERIFY(is); + constexpr MeshAttribute a = meshAttributeCustom(8290); + CORRADE_COMPARE(UnsignedShort(a), 41058); + constexpr UnsignedShort b = meshAttributeCustom(a); + CORRADE_COMPARE(b, 8290); +} + +void MeshDataTest::customAttributeNameTooLarge() { + std::ostringstream out; + Error redirectError{&out}; + meshAttributeCustom(32768); + CORRADE_COMPARE(out.str(), "Trade::meshAttributeCustom(): index 32768 too large\n"); +} + +void MeshDataTest::customAttributeNameNotCustom() { + std::ostringstream out; + Error redirectError{&out}; + meshAttributeCustom(MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(out.str(), "Trade::meshAttributeCustom(): Trade::MeshAttribute::TextureCoordinates is not custom\n"); +} + +void MeshDataTest::debugAttributeName() { + std::ostringstream out; + Debug{&out} << MeshAttribute::Position << meshAttributeCustom(73) << MeshAttribute(0x73); + CORRADE_COMPARE(out.str(), "Trade::MeshAttribute::Position Trade::MeshAttribute::Custom(73) Trade::MeshAttribute(0x73)\n"); +} + +using namespace Math::Literals; + +constexpr UnsignedByte IndexBytes[]{25, 132, 3}; +constexpr UnsignedShort IndexShorts[]{2575, 13224, 3}; +constexpr UnsignedInt IndexInts[]{2110122, 132257, 3}; + +void MeshDataTest::constructIndex() { + { + const UnsignedByte indexData[]{25, 132, 3}; + MeshIndexData indices{indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(indices.data().data(), indexData); + + constexpr MeshIndexData cindices{IndexBytes}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::ArrayView data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); + CORRADE_COMPARE(data.data(), IndexBytes); + } { + const UnsignedShort indexData[]{2575, 13224, 3}; + MeshIndexData indices{indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(indices.data().data(), indexData); + + constexpr MeshIndexData cindices{IndexShorts}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::ArrayView data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.data(), IndexShorts); + } { + const UnsignedInt indexData[]{2110122, 132257, 3}; + MeshIndexData indices{indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(indices.data().data(), indexData); + + constexpr MeshIndexData cindices{IndexInts}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::ArrayView data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.data(), IndexInts); + } +} + +void MeshDataTest::constructIndexTypeErased() { + const char indexData[3*2]{}; + MeshIndexData indices{MeshIndexType::UnsignedShort, indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_VERIFY(indices.data().data() == indexData); +} + +void MeshDataTest::constructIndexTypeErasedWrongSize() { + const char indexData[3*2]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{MeshIndexType::UnsignedInt, indexData}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view size 6 does not correspond to MeshIndexType::UnsignedInt\n"); +} + +void MeshDataTest::constructIndex2D() { + { + const UnsignedByte indexData[]{25, 132, 3}; + MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(indices.data().data(), indexData); + } { + const UnsignedShort indexData[]{2575, 13224, 3}; + MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(indices.data().data(), indexData); + } { + const UnsignedInt indexData[]{2110122, 132257, 3}; + MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(indices.data().data(), indexData); + } +} + +void MeshDataTest::constructIndex2DWrongSize() { + const char data[3*3]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{Containers::StridedArrayView2D{data, {3, 3}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: expected index type size 1, 2 or 4 but got 3\n"); +} + +void MeshDataTest::constructIndex2DNonContiguous() { + const char data[3*4]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{Containers::StridedArrayView2D{data, {3, 2}, {4, 2}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view is not contiguous\n"); +} + +void MeshDataTest::constructIndexNullptr() { + /* Just verify it's not ambiguous */ + MeshIndexData data{nullptr}; + CORRADE_VERIFY(!data.data()); +} + +constexpr Vector2 Positions[] { + {1.2f, 0.2f}, + {2.2f, 1.1f}, + {-0.2f, 7.2f} +}; + +void MeshDataTest::constructAttribute() { + const Vector2 positionData[3]; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_COMPARE(positions.offset(positionData), 0); + CORRADE_COMPARE(positions.stride(), sizeof(Vector2)); + CORRADE_VERIFY(positions.data().data() == positionData); + /* This is allowed too for simplicity, it just ignores the parameter */ + CORRADE_VERIFY(positions.data(positionData).data() == positionData); + + constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; + constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); + constexpr UnsignedShort arraySize = cpositions.arraySize(); + constexpr MeshAttribute name = cpositions.name(); + constexpr VertexFormat format = cpositions.format(); + constexpr Short stride = cpositions.stride(); + constexpr Containers::StridedArrayView1D data = cpositions.data(); + CORRADE_VERIFY(!isOffsetOnly); + CORRADE_COMPARE(arraySize, 0); + CORRADE_COMPARE(name, MeshAttribute::Position); + CORRADE_COMPARE(format, VertexFormat::Vector2); + CORRADE_COMPARE(stride, sizeof(Vector2)); + CORRADE_COMPARE(data.data(), Positions); +} + +void MeshDataTest::constructAttributeCustom() { + const Short idData[3]{}; + MeshAttributeData ids{meshAttributeCustom(13), Containers::arrayView(idData)}; + CORRADE_COMPARE(ids.name(), meshAttributeCustom(13)); + CORRADE_COMPARE(ids.format(), VertexFormat::Short); + CORRADE_VERIFY(ids.data().data() == idData); +} + +void MeshDataTest::constructAttribute2D() { + char positionData[4*sizeof(Vector2)]{}; + auto positionView = Containers::StridedArrayView2D{positionData, + {4, sizeof(Vector2)}}.every(2); + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_COMPARE(positions.data().data(), positionView.data()); +} + +void MeshDataTest::constructAttribute2DWrongSize() { + char positionData[4*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, + Containers::StridedArrayView2D{positionData, + {4, sizeof(Vector2)}}.every(2)}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension size 8 doesn't match VertexFormat::Vector3\n"); +} + +void MeshDataTest::constructAttribute2DNonContiguous() { + char positionData[4*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, + Containers::StridedArrayView2D{positionData, + {2, sizeof(Vector2)*2}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructAttributeTypeErased() { + const Vector3 positionData[3]{}; + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionData))}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); + CORRADE_VERIFY(positions.data().data() == positionData); +} + +void MeshDataTest::constructAttributeNullptr() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_VERIFY(!positions.data().data()); +} + +void MeshDataTest::constructAttributePadding() { + MeshAttributeData padding{-35}; + CORRADE_VERIFY(!padding.isOffsetOnly()); + CORRADE_COMPARE(padding.arraySize(), 0); + CORRADE_COMPARE(padding.name(), MeshAttribute{}); + CORRADE_COMPARE(padding.format(), VertexFormat{}); + CORRADE_COMPARE(padding.data().size(), 0); + CORRADE_COMPARE(padding.data().stride(), -35); + CORRADE_VERIFY(!padding.data()); +} + +void MeshDataTest::constructAttributeNonOwningArray() { + const MeshAttributeData data[3]; + Containers::Array array = meshAttributeDataNonOwningArray(data); + CORRADE_COMPARE(array.size(), 3); + CORRADE_COMPARE(static_cast(array.data()), data); +} + +void MeshDataTest::constructAttributeOffsetOnly() { + struct { + Vector2 position; + Vector2 textureCoordinates; + } vertexData[] { + {{}, {1.0f, 0.3f}}, + {{}, {0.5f, 0.7f}}, + }; + + MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; + CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.arraySize(), 0); + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), VertexFormat::Vector2); + CORRADE_COMPARE(a.offset(vertexData), sizeof(Vector2)); + CORRADE_COMPARE(a.stride(), 2*sizeof(Vector2)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); + + constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; + CORRADE_VERIFY(ca.isOffsetOnly()); + CORRADE_COMPARE(ca.arraySize(), 0); + CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); + CORRADE_COMPARE(ca.offset(vertexData), sizeof(Vector2)); + CORRADE_COMPARE(ca.stride(), 2*sizeof(Vector2)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::constructAttributeImplementationSpecificFormat() { + Vector2 positions[]{{1.0f, 0.3f}, {0.5f, 0.7f}}; + + /* This should not fire any asserts */ + MeshAttributeData a{MeshAttribute::TextureCoordinates, vertexFormatWrap(0x3a), positions}; + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), vertexFormatWrap(0x3a)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data()), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::constructAttributeWrongFormat() { + Vector2 positionData[3]; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)}; + MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector2, 0, 3, sizeof(Vector2)}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n" + "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); +} + +void MeshDataTest::constructAttributeWrongStride() { + char positionData[3*sizeof(Vector3)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; + /* We need this one to be constexpr, which means there can't be a warning + about stride not matching the size */ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, 0, 3*sizeof(Vector3), 1}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, -16}}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, 65000}}; + MeshAttributeData{65000}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: expected stride to be positive and enough to fit VertexFormat::Vector3, got 1\n" + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got -16\n" + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got 65000\n" + "Trade::MeshAttributeData: at most 32k padding supported, got 65000\n" + ); +} + +void MeshDataTest::constructAttributeWrongDataAccess() { + Vector2 positionData[3]; + MeshAttributeData a{MeshAttribute::Position, Containers::arrayView(positionData)}; + MeshAttributeData b{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, sizeof(Vector2)}; + CORRADE_VERIFY(!a.isOffsetOnly()); + CORRADE_VERIFY(b.isOffsetOnly()); + + std::ostringstream out; + Error redirectError{&out}; + b.data(); + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); +} + +constexpr Vector2 ArrayVertexData[3*4] + /* MSVC 2015 needs an explicit initializer. GCC 4.8 *doesn't*. */ + #ifdef CORRADE_MSVC2015_COMPATIBILITY + {} + #endif + ; + +void MeshDataTest::constructArrayAttribute() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView2D attribute{vertexData, {3, 4}}; + MeshAttributeData data{meshAttributeCustom(35), attribute}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); + + constexpr Containers::StridedArrayView2D cattribute{ArrayVertexData, {3, 4}}; + constexpr MeshAttributeData cdata{meshAttributeCustom(35), cattribute}; + CORRADE_VERIFY(!cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_VERIFY(cdata.data().data() == ArrayVertexData); + CORRADE_COMPARE(cdata.data().size(), 3); + CORRADE_COMPARE(cdata.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeNonContiguous() { + Vector2 vertexData[4*3]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), + Containers::StridedArrayView2D{vertexData, + {4, 3}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructArrayAttribute2D() { + char vertexData[3*4*sizeof(Vector2)]; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, 4, Containers::StridedArrayView2D{vertexData, {3, 4*sizeof(Vector2)}}}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttribute2DWrongSize() { + char vertexData[3*4*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 3, + Containers::StridedArrayView2D{vertexData, + {3, 4*sizeof(Vector2)}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension size 32 doesn't match VertexFormat::Vector2 and array size 3\n"); +} + +void MeshDataTest::constructArrayAttribute2DNonContiguous() { + char vertexData[4*3*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 2, + Containers::StridedArrayView2D{vertexData, + {3, sizeof(Vector2)*4}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructArrayAttributeTypeErased() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView1D attribute{vertexData, 3, 4*sizeof(Vector2)}; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, 4, attribute}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeOffsetOnly() { + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4}; + CORRADE_VERIFY(data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + + Vector2 vertexData[1 + 3*4]; + CORRADE_VERIFY(data.data(vertexData).data() == vertexData + 1); + CORRADE_COMPARE(data.data(vertexData).size(), 3); + CORRADE_COMPARE(data.data(vertexData).stride(), sizeof(Vector2)); + + constexpr MeshAttributeData cdata{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4}; + CORRADE_VERIFY(cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); +} + +void MeshDataTest::constructArrayAttributeNotAllowed() { + Vector2 positionData[3*3]; + Containers::ArrayView positions{positionData}; + Containers::StridedArrayView2D positions2D{positionData, {3, 3}}; + auto positions2Dchar = Containers::arrayCast<2, const char>(positions2D); + + /* This is all fine */ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, positions}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 0}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 0, positions}; + MeshAttributeData{meshAttributeCustom(35), positions2D}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 3, positions2Dchar}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 3}; + + /* This is not */ + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 3, Containers::arrayView(positionData)}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 3, Containers::arrayView(positionData)}; + MeshAttributeData{MeshAttribute::Position, positions2D}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 3, positions2Dchar}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 3}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 0, 3, 6*sizeof(Vector2), 3}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format\n"); +} + +void MeshDataTest::construct() { + auto&& instanceData = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinate; + Short id; + }; + + Containers::Array indexData{8*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData).slice(1, 7); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 2; + indexView[3] = 0; + indexView[4] = 2; + indexView[5] = 1; + + /* Enough vertex data to fit also the case with large explicit vertex count + (but fill just the first 3, as those are only tested) */ + Containers::Array vertexData{17*sizeof(Vertex)}; + auto vertexView = Containers::arrayCast(vertexData).prefix(3); + vertexView[0].position = {0.1f, 0.2f, 0.3f}; + vertexView[1].position = {0.4f, 0.5f, 0.6f}; + vertexView[2].position = {0.7f, 0.8f, 0.9f}; + vertexView[0].normal = Vector3::xAxis(); + vertexView[1].normal = Vector3::yAxis(); + vertexView[2].normal = Vector3::zAxis(); + vertexView[0].textureCoordinate = {0.000f, 0.125f}; + vertexView[1].textureCoordinate = {0.250f, 0.375f}; + vertexView[2].textureCoordinate = {0.500f, 0.625f}; + vertexView[0].id = 15; + vertexView[1].id = -374; + vertexView[2].id = 22; + + if(instanceData.vertexCount < 3) + vertexView = vertexView.prefix(instanceData.vertexCount); + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}}; + /* Using a relative offset */ + MeshAttributeData normals{MeshAttribute::Normal, + VertexFormat::Vector3, offsetof(Vertex, normal), + UnsignedInt(vertexView.size()), sizeof(Vertex)}; + MeshAttributeData textureCoordinates{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}}; + MeshAttributeData ids{meshAttributeCustom(13), + Containers::StridedArrayView1D{vertexData, &vertexView[0].id, vertexView.size(), sizeof(Vertex)}}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + /* Texture coordinates deliberately twice (though aliased) */ + std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates, ids}, instanceData.vertexCount, &importerState}; + + /* Basics */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(!data.attributeData().empty()); + CORRADE_COMPARE(static_cast(data.indexData() + 2), indexView.data()); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + CORRADE_COMPARE(static_cast(data.mutableIndexData() + 2), indexView.data()); + CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); + CORRADE_COMPARE(data.importerState(), &importerState); + + /* Index access */ + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 6); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indexOffset(), 2); + + /* Typeless index access with a cast later */ + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[1]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[3]), 0); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[4]), 2); + + /* Typed index access */ + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[2], 2); + CORRADE_COMPARE(data.indices()[5], 1); + + /* Attribute access by ID */ + CORRADE_COMPARE(data.vertexCount(), instanceData.expectedVertexCount); + CORRADE_COMPARE(data.attributeCount(), 5); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeName(1), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeName(2), MeshAttribute::Normal); + CORRADE_COMPARE(data.attributeName(3), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeName(4), meshAttributeCustom(13)); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(1), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(2), VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(3), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(4), VertexFormat::Short); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(2), sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(3), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(4), 2*sizeof(Vector3) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeArraySize(0), 0); + CORRADE_COMPARE(data.attributeArraySize(1), 0); + CORRADE_COMPARE(data.attributeArraySize(2), 0); + CORRADE_COMPARE(data.attributeArraySize(3), 0); + CORRADE_COMPARE(data.attributeArraySize(4), 0); + + /* Typeless access by ID with a cast later */ + CORRADE_COMPARE(data.attribute(0).size()[0], instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(0).size()[0], instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(1))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(2))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(3))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Short>( + data.attribute(4))[0]), 15); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(1))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(2))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(3))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, Short>( + data.mutableAttribute(4))[0]), 15); + } + + /* Typed access by ID */ + CORRADE_COMPARE(data.attribute(0).size(), instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(0).size(), instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE(data.attribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(4)[1], -374); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); + } + + /* Raw attribute data access by ID */ + CORRADE_COMPARE(data.attributeData(3).name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeData(3).format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeData(3).data().size(), instanceData.expectedVertexCount); + if(instanceData.vertexCount) + CORRADE_COMPARE(Containers::arrayCast(data.attributeData(3).data())[1], (Vector2{0.250f, 0.375f})); + + /* Attribute access by name */ + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal)); + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::TextureCoordinates)); + CORRADE_VERIFY(data.hasAttribute(meshAttributeCustom(13))); + CORRADE_VERIFY(!data.hasAttribute(MeshAttribute::Color)); + CORRADE_VERIFY(!data.hasAttribute(meshAttributeCustom(23))); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Normal), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::TextureCoordinates), 2); + CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(13)), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Color), 0); + CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(23)), 0); + CORRADE_COMPARE(data.attributeId(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeId(MeshAttribute::Normal), 2); + CORRADE_COMPARE(data.attributeId(MeshAttribute::TextureCoordinates), 1); + CORRADE_COMPARE(data.attributeId(MeshAttribute::TextureCoordinates, 1), 3); + CORRADE_COMPARE(data.attributeId(meshAttributeCustom(13)), 4); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), + VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Normal), + VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::TextureCoordinates, 0), + VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::TextureCoordinates, 1), + VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(meshAttributeCustom(13)), + VertexFormat::Short); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Normal), sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 0), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 1), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(meshAttributeCustom(13)), 2*sizeof(Vector3) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::Position), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::Normal), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Normal), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 0), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 1), 0); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(13)), 0); + + /* Typeless access by name with a cast later */ + CORRADE_COMPARE(data.attribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Short>( + data.attribute(meshAttributeCustom(13)))[1]), -374); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.mutableAttribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, Short>( + data.mutableAttribute(meshAttributeCustom(13)))[1]), -374); + } + + /* Typed access by name */ + CORRADE_COMPARE(data.attribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + } +} + +void MeshDataTest::constructZeroIndices() { + /* This is a valid use case because this could be an empty slice of a + well-defined indexed mesh. Explicitly use a non-null zero-sized array + to check the importer is checking size and not pointer. */ + Containers::Array vertexData{3*sizeof(Vector3)}; + auto vertices = Containers::arrayCast(vertexData); + char i; + Containers::Array indexData{&i, 0, [](char*, std::size_t){}}; + auto indices = Containers::arrayCast(indexData); + MeshAttributeData positions{MeshAttribute::Position, vertices}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), MeshIndexData{indices}, + std::move(vertexData), {positions}}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.indexCount(), 0); + CORRADE_COMPARE(data.vertexCount(), 3); +} + +void MeshDataTest::constructZeroAttributes() { + /* This is a valid use case because e.g. the index/vertex data can be + shared by multiple meshes and this particular one is just a plain + index array */ + Containers::Array indexData{3*sizeof(UnsignedInt)}; + Containers::Array vertexData{3}; + auto indexView = Containers::arrayCast(indexData); + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), MeshIndexData{indexView}, + std::move(vertexData), {}, 15}; + + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.attributeCount(), 0); + CORRADE_VERIFY(!data.attributeData()); + CORRADE_COMPARE(data.vertexData().size(), 3); + CORRADE_COMPARE(data.vertexCount(), 15); +} + +void MeshDataTest::constructZeroVertices() { + /* This is a valid use case because this could be an empty slice of a + well-defined indexed mesh */ + Containers::Array indexData{3*sizeof(UnsignedInt)}; + auto indexView = Containers::arrayCast(indexData); + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), MeshIndexData{indexView}, + nullptr, {positions}}; + + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); + CORRADE_COMPARE(data.attribute(0).size(), 0); + CORRADE_VERIFY(!data.vertexData()); + CORRADE_COMPARE(data.vertexCount(), 0); +} + +void MeshDataTest::constructIndexless() { + Containers::Array vertexData{3*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + vertexView[2] = {0.7f, 0.8f}; + + int importerState; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + /* These are empty so it doesn't matter, but this is a nice non-restrictive + default */ + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructIndexlessZeroVertices() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + MeshData data{MeshPrimitive::LineLoop, nullptr, {positions}}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); +} + +void MeshDataTest::constructAttributeless() { + Containers::Array indexData{6*sizeof(UnsignedInt)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 2; + indexView[3] = 0; + indexView[4] = 2; + indexView[5] = 1; + + int importerState; + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, 3, &importerState}; + /* These are empty so it doesn't matter, but this is a nice non-restrictive + default */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 6); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[2], 2); + CORRADE_COMPARE(data.indices()[5], 1); + + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, instanceData.indexDataFlags, Containers::arrayView(indexData), indices, instanceData.vertexDataFlags, Containers::arrayView(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), instanceData.indexDataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.vertexDataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), +indexData); + CORRADE_COMPARE(static_cast(data.vertexData()), +vertexData); + if(instanceData.indexDataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableIndexData()), +indexData); + if(instanceData.vertexDataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableVertexData()), +vertexData); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.indexDataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.vertexDataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); + } +} + +void MeshDataTest::constructIndicesNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + int importerState; + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::Triangles, instanceData.dataFlags, Containers::arrayView(indexData), indices, std::move(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), +indexData); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableIndexData()), +indexData); + CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructVerticesNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, instanceData.dataFlags, Containers::arrayView(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.vertexData()), +vertexData); + CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableVertexData()), +vertexData); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); + } +} + +void MeshDataTest::constructIndexlessNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::LineLoop, instanceData.dataFlags, vertexData, {positions}, MeshData::ImplicitVertexCount, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableIndexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructAttributelessNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + + int importerState; + MeshIndexData indices{indexData}; + MeshData data{MeshPrimitive::TriangleStrip, instanceData.dataFlags, indexData, indices, 5, &importerState}; + CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); + CORRADE_COMPARE(data.vertexData(), nullptr); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableVertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[0], 0); + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 5); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexlessAttributeless() { + int importerState; + MeshData data{MeshPrimitive::TriangleStrip, 37, &importerState}; + /* These are both empty so it doesn't matter, but this is a nice + non-restrictive default */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 37); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexlessAttributelessZeroVertices() { + int importerState; + MeshData data{MeshPrimitive::TriangleStrip, 0, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexDataButNotIndexed() { + Containers::Array indexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{}, nullptr, {positions}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: indexData passed for a non-indexed mesh\n"); +} + +void MeshDataTest::constructAttributelessImplicitVertexCount() { + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Points, nullptr, {}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: vertex count can't be implicit if there are no attributes\n"); +} + +void MeshDataTest::constructIndicesNotContained() { + Containers::Array indexData{reinterpret_cast(0xbadda9), 6, [](char*, std::size_t){}}; + Containers::ArrayView indexData2{reinterpret_cast(0xdead), 3}; + MeshIndexData indices{indexData2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(indexData), indices, 1}; + MeshData{MeshPrimitive::Triangles, nullptr, indices, 1}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" + "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0x0:0x0]\n"); +} + +void MeshDataTest::constructAttributeNotContained() { + Containers::Array vertexData{reinterpret_cast(0xbadda9), 24, [](char*, std::size_t){}}; + Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; + MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)}; + MeshAttributeData positions3{MeshAttribute::Position, VertexFormat::Vector2, 1, 3, 8}; + /* See implementationSpecificVertexFormatNotContained() below for + implementation-specific formats */ + + /* Here the original positions array is shrunk from 3 items to 2 and the + vertex data too, which should work without asserting -- comparing just + the original view would not pass, which is wrong */ + MeshData{MeshPrimitive::Triangles, {}, vertexData.prefix(16), {positions}, 2}; + + std::ostringstream out; + Error redirectError{&out}; + /* Here the original positions array is extended from 3 items to 4, which + makes it not fit anymore, and thus an assert should hit -- comparing + just the original view would pass, which is wrong */ + MeshData{MeshPrimitive::Triangles, {}, vertexData, {positions}, 4}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + MeshData{MeshPrimitive::Triangles, nullptr, {positions}}; + MeshData{MeshPrimitive::Triangles, Containers::Array{24}, {positions3}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc9] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" + "Trade::MeshData: attribute 1 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" + "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n" + "Trade::MeshData: offset attribute 0 spans 25 bytes but passed vertexData array has only 24\n"); +} + +void MeshDataTest::constructInconsitentVertexCount() { + Containers::Array vertexData{136}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData).prefix(3)}; + MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayCast(vertexData).prefix(2)}; + + std::ostringstream out; + Error redirectError{&out}; + /* The explicit vertex count should be ignored for the assertion message, + we only check that all passed attribute arrays have the same vertex + count. However, the actual "containment" of the attribute views is + checked with the explicit vertex count -- see the + constructAttributeNotContained() test above. */ + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}, 17}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 1 has 2 vertices but 3 expected\n"); +} + +void MeshDataTest::constructNotOwnedIndexFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, {}, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructNotOwnedVertexFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, {}, indexData, indices, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructIndicesNotOwnedFlagOwned() { + UnsignedShort indexData[]{0, 1, 0}; + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, std::move(vertexData), {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructVerticesNotOwnedFlagOwned() { + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructIndexlessNotOwnedFlagOwned() { + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructAttributelessNotOwnedFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + MeshIndexData indices{indexData}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, 2}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructInvalidAttributeData() { + MeshAttributeData a; + MeshAttributeData b{3}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, nullptr, {a}}; + MeshData{MeshPrimitive::Triangles, nullptr, {b}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 0 doesn't specify anything\n" + "Trade::MeshData: attribute 0 doesn't specify anything\n"); +} + +void MeshDataTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void MeshDataTest::constructMove() { + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData a{MeshPrimitive::Triangles, std::move(indexData), indices, std::move(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; + + MeshData b{std::move(a)}; + + CORRADE_COMPARE(b.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(b.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(b.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(b.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(b.vertexData()), vertexView.data()); + CORRADE_COMPARE(b.importerState(), &importerState); + + CORRADE_VERIFY(b.isIndexed()); + CORRADE_COMPARE(b.indexCount(), 3); + CORRADE_COMPARE(b.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(b.indices()[1], 1); + CORRADE_COMPARE(b.indices()[2], 0); + + CORRADE_COMPARE(b.vertexCount(), 2); + CORRADE_COMPARE(b.attributeCount(), 1); + CORRADE_COMPARE(b.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(b.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(b.attributeOffset(0), 0); + CORRADE_COMPARE(b.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(b.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(b.attribute(0)[1], (Vector2{0.4f, 0.5f})); + + MeshData c{MeshPrimitive::LineLoop, 37}; + c = std::move(b); + + CORRADE_COMPARE(c.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(c.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(c.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(c.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(c.vertexData()), vertexView.data()); + CORRADE_COMPARE(c.importerState(), &importerState); + + CORRADE_VERIFY(c.isIndexed()); + CORRADE_COMPARE(c.indexCount(), 3); + CORRADE_COMPARE(c.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(c.indices()[1], 1); + CORRADE_COMPARE(c.indices()[2], 0); + + CORRADE_COMPARE(c.vertexCount(), 2); + CORRADE_COMPARE(c.attributeCount(), 1); + CORRADE_COMPARE(c.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(c.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(c.attributeOffset(0), 0); + CORRADE_COMPARE(c.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(c.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(c.attribute(0)[1], (Vector2{0.4f, 0.5f})); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +template struct NameTraits; +#define _c(format) template<> struct NameTraits { \ + static const char* name() { return #format; } \ + }; +_c(Vector2) +_c(Vector2h) +_c(Vector2ub) +_c(Vector2b) +_c(Vector2us) +_c(Vector2s) +_c(Vector3) +_c(Vector3h) +_c(Vector3ub) +_c(Vector3b) +_c(Vector3us) +_c(Vector3s) +_c(Color3) +_c(Color3h) +_c(Color3ub) +_c(Color3us) +_c(Color4) +_c(Color4h) +_c(Color4ub) +_c(Color4us) +#undef _c + +template void MeshDataTest::indicesAsArray() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + Containers::Array indexData{3*sizeof(T)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 75; + indexView[1] = 131; + indexView[2] = 240; + + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}, 241}; + CORRADE_COMPARE_AS(data.indicesAsArray(), + Containers::arrayView({75, 131, 240}), + TestSuite::Compare::Container); +} + +void MeshDataTest::indicesIntoArrayInvalidSize() { + Containers::Array indexData{3*sizeof(UnsignedInt)}; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}, 1}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + data.indicesInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::indicesInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::positions2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{U(2.0f), U(1.0f)}); + positionsView[1] = T::pad(Math::Vector2{U(0.0f), U(-1.0f)}); + positionsView[2] = T::pad(Math::Vector2{U(-2.0f), U(3.0f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedUnsigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{2, 1}); + positionsView[1] = T::pad(Math::Vector2{0, 15}); + positionsView[2] = T::pad(Math::Vector2{22, 3}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, 15.0f}, {22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedSigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{2, 1}); + positionsView[1] = T::pad(Math::Vector2{0, -15}); + positionsView[2] = T::pad(Math::Vector2{-22, 3}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, -15.0f}, {-22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{Math::pack(1.0f), 0}); + positionsView[1] = T::pad(Math::Vector2{0, Math::pack(1.0f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, 1.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{Math::pack(1.0f), 0}); + positionsView[1] = T::pad(Math::Vector2{0, Math::pack(-1.0f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, -1.0f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::positions2DIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector2)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector2 destination[2]; + data.positions2DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::positions2DInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::positions3DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + positionsView[0] = T::pad(Math::Vector3{U(2.0f), U(1.0f), U(0.75f)}); + positionsView[1] = T::pad(Math::Vector3{U(0.0f), U(-1.0f), U(1.25f)}); + positionsView[2] = T::pad(Math::Vector3{U(-2.0f), U(3.0f), U(2.5f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, 0.75f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, -1.0f, 1.25f})), + Vector3::pad(Math::Vector::pad(Vector3{-2.0f, 3.0f, 2.5f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedUnsigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{2, 1, 135}); + positionsView[1] = T::pad(Math::Vector3{0, 15, 2}); + positionsView[2] = T::pad(Math::Vector3{22, 3, 192}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, 135.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, 15.0f, 2.0f})), + Vector3::pad(Math::Vector::pad(Vector3{22.0f, 3.0f, 192.0f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedSigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{2, 1, -117}); + positionsView[1] = T::pad(Math::Vector3{0, -15, 2}); + positionsView[2] = T::pad(Math::Vector3{-22, 3, 86}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, -117.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, -15.0f, 2.0f})), + Vector3::pad(Math::Vector::pad(Vector3{-22.0f, 3.0f, 86.0f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{Math::pack(1.0f), 0, Math::pack(1.0f)}); + positionsView[1] = T::pad(Math::Vector3{0, Math::pack(1.0f), 0}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{1.0f, 0.0f, 1.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, 1.0f, 0.0f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{Math::pack(1.0f), 0, Math::pack(1.0f)}); + positionsView[1] = T::pad(Math::Vector3{0, Math::pack(-1.0f), 0}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{1.0f, 0.0f, 1.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, -1.0f, 0.0f})) + }), TestSuite::Compare::Container); +} + +void MeshDataTest::positions3DIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector3)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 destination[2]; + data.positions3DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::positions3DInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::normalsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto normalsView = Containers::arrayCast(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + normalsView[0] = {U(2.0f), U(1.0f), U(0.75f)}; + normalsView[1] = {U(0.0f), U(-1.0f), U(1.25f)}; + normalsView[2] = {U(-2.0f), U(3.0f), U(2.5f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, normalsView}}}; + CORRADE_COMPARE_AS(data.normalsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::normalsAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto normalsView = Containers::arrayCast(vertexData); + normalsView[0] = {Math::pack(1.0f), 0, Math::pack(1.0f)}; + normalsView[1] = {0, Math::pack(-1.0f), 0}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + normalsView}}}; + CORRADE_COMPARE_AS(data.normalsAsArray(), Containers::arrayView({ + {1.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f} + }), TestSuite::Compare::Container); +} + +void MeshDataTest::normalsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector3)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 destination[2]; + data.normalsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::normalsInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::textureCoordinates2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {U(2.0f), U(1.0f)}; + textureCoordinatesView[1] = {U(0.0f), U(-1.0f)}; + textureCoordinatesView[2] = {U(-2.0f), U(3.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), Containers::arrayView({ + {2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {2, 1}; + textureCoordinatesView[1] = {0, 15}; + textureCoordinatesView[2] = {22, 3}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, 15.0f}, {22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedSigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {2, 1}; + textureCoordinatesView[1] = {0, -15}; + textureCoordinatesView[2] = {-22, 3}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, -15.0f}, {-22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {Math::pack(1.0f), 0}; + textureCoordinatesView[1] = {0, Math::pack(1.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, 1.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {Math::pack(1.0f), 0}; + textureCoordinatesView[1] = {0, Math::pack(-1.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, -1.0f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::textureCoordinates2DIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector2)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector2 destination[2]; + data.textureCoordinates2DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::textureCoordinates2DInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::colorsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto colorsView = Containers::arrayCast(vertexData); + /* Can't use e.g. 0xff3366_rgbf because that's not representable in + half-floats */ + colorsView[0] = {U(2.0f), U(1.0f), U(0.75f)}; + colorsView[1] = {U(0.0f), U(-1.0f), U(1.25f)}; + colorsView[2] = {U(-2.0f), U(3.0f), U(2.5f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, colorsView}}}; + CORRADE_COMPARE_AS(data.colorsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::colorsAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto colorsView = Containers::arrayCast(vertexData); + colorsView[0] = T::pad(Math::Color4{Math::pack(1.0f), 0, Math::pack(1.0f), 0}); + colorsView[1] = T::pad(Math::Color4{0, Math::pack(1.0f), 0, Math::pack(1.0f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, colorsView}}}; + CORRADE_COMPARE_AS(data.colorsAsArray(), Containers::arrayView({ + Color4::pad(Math::Vector::pad(Vector4{1.0f, 0.0f, 1.0f, 0.0f}), 1.0f), + Color4::pad(Math::Vector::pad(Vector4{0.0f, 1.0f, 0.0f, 1.0f}), 1.0f) + }), TestSuite::Compare::Container); +} + +void MeshDataTest::colorsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Color4)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Color4 destination[2]; + data.colorsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); +} + +/* MSVC 2015 doesn't like anonymous bitfields in inline structs, so putting the + declaration outside */ +struct VertexWithImplementationSpecificData { + Long:64; + long double thing; +}; + +void MeshDataTest::implementationSpecificVertexFormat() { + VertexWithImplementationSpecificData vertexData[] { + {456.0l}, + {456.0l} + }; + + /* Constructing should work w/o asserts */ + Containers::StridedArrayView1D attribute{vertexData, + &vertexData[0].thing, 2, sizeof(VertexWithImplementationSpecificData)}; + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, + vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Normal, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + vertexFormatWrap(0xdead3), attribute}, + MeshAttributeData{MeshAttribute::Color, + vertexFormatWrap(0xdead4), attribute}}}; + + /* Getting typeless attribute should work also */ + UnsignedInt format = 0xdead1; + for(MeshAttribute name: {MeshAttribute::Position, + MeshAttribute::Normal, + MeshAttribute::TextureCoordinates, + MeshAttribute::Color}) { + CORRADE_ITERATION(name); + CORRADE_COMPARE(data.attributeFormat(name), vertexFormatWrap(format++)); + + /* The actual type size is unknown, so this will use the full stride */ + CORRADE_COMPARE(data.attribute(name).size()[1], sizeof(VertexWithImplementationSpecificData)); + + CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( + data.attribute(name).prefix({2, sizeof(long double)}))), + attribute, TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( + data.mutableAttribute(name).prefix({2, sizeof(long double)}))), + attribute, TestSuite::Compare::Container); + } +} + +void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { + VertexWithImplementationSpecificData vertexData[] { + {456.0l}, + {456.0l} + }; + + Containers::StridedArrayView1D attribute{vertexData, + &vertexData[0].thing, 2, sizeof(VertexWithImplementationSpecificData)}; + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, + vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Normal, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + vertexFormatWrap(0xdead3), attribute}, + MeshAttributeData{MeshAttribute::Color, + vertexFormatWrap(0xdead4), attribute}}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Normal); + data.attribute(MeshAttribute::TextureCoordinates); + data.attribute(MeshAttribute::Color); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(MeshAttribute::Normal); + data.mutableAttribute(MeshAttribute::TextureCoordinates); + data.mutableAttribute(MeshAttribute::Color); + data.positions2DAsArray(); + data.positions3DAsArray(); + data.normalsAsArray(); + data.textureCoordinates2DAsArray(); + data.colorsAsArray(); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" + "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::textureCoordinatesInto(): can't extract data out of an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format 0xdead4\n"); +} + +void MeshDataTest::implementationSpecificVertexFormatNotContained() { + Containers::Array vertexData{reinterpret_cast(0xbadda9), 3, [](char*, std::size_t){}}; + Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; + MeshAttributeData positions{MeshAttribute::Position, vertexFormatWrap(0x3a), vertexData}; + MeshAttributeData positions2{MeshAttribute::Position, vertexFormatWrap(0x3a), vertexData2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + CORRADE_COMPARE(out.str(), + /* Assumes size of the type is 0, so the diagnostic is different from + constructAttributeNotContained() */ + "Trade::MeshData: attribute 1 [0xdead:0xdeaf] is not contained in passed vertexData array [0xbadda9:0xbaddac]\n"); +} + +void MeshDataTest::arrayAttribute() { + Vector2 vertexData[3*4]{ + {1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}, {7.0f, 8.0f}, + {1.1f, 2.2f}, {3.3f, 4.4f}, {5.5f, 6.6f}, {7.7f, 8.8f}, + {0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}, {0.7f, 0.8f}, + }; + Containers::StridedArrayView2D positions2D{vertexData, {3, 4}}; + + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{meshAttributeCustom(35), positions2D} + }}; + + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(35)), 4); + + /* Raw access is "as usual" */ + auto attribute = Containers::arrayCast<2, const Vector2>(data.attribute(0)); + auto attributeByName = Containers::arrayCast<2, const Vector2>(data.attribute(meshAttributeCustom(35))); + auto mutableAttribute = Containers::arrayCast<2, Vector2>(data.mutableAttribute(0)); + auto mutableAttributeByName = Containers::arrayCast<2, Vector2>(data.mutableAttribute(meshAttributeCustom(35))); + CORRADE_COMPARE(attribute.size()[0], 3); + CORRADE_COMPARE(attributeByName.size()[0], 3); + CORRADE_COMPARE(mutableAttribute.size()[0], 3); + CORRADE_COMPARE(mutableAttributeByName.size()[0], 3); + for(std::size_t i = 0; i != 3; ++i) { + CORRADE_ITERATION(i); + CORRADE_COMPARE_AS(attribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(attributeByName[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttributeByName[i], positions2D[i], + TestSuite::Compare::Container); + } + + /* Typed access */ + attribute = data.attribute(0); + attributeByName = data.attribute(meshAttributeCustom(35)); + mutableAttribute = data.mutableAttribute(0); + mutableAttributeByName = data.mutableAttribute(meshAttributeCustom(35)); + CORRADE_COMPARE(attribute.size()[0], 3); + CORRADE_COMPARE(attributeByName.size()[0], 3); + CORRADE_COMPARE(mutableAttribute.size()[0], 3); + CORRADE_COMPARE(mutableAttributeByName.size()[0], 3); + for(std::size_t i = 0; i != 3; ++i) { + CORRADE_ITERATION(i); + CORRADE_COMPARE_AS(attribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(attributeByName[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttributeByName[i], positions2D[i], + TestSuite::Compare::Container); + } +} + +void MeshDataTest::arrayAttributeWrongAccess() { + Vector2 vertexData[3*4]{ + {1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}, {7.0f, 8.0f}, + {1.1f, 2.2f}, {3.3f, 4.4f}, {5.5f, 6.6f}, {7.7f, 8.8f}, + {0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}, {0.7f, 0.8f}, + }; + Containers::StridedArrayView1D positions{vertexData, 3, 4*sizeof(Vector2)}; + Containers::StridedArrayView2D positions2D{vertexData, {3, 4}}; + + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, positions}, + MeshAttributeData{meshAttributeCustom(35), positions2D} + }}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(0); + data.attribute(1); + data.mutableAttribute(0); + data.mutableAttribute(1); + data.attribute(MeshAttribute::Position); + data.attribute(meshAttributeCustom(35)); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(meshAttributeCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n"); +} + +void MeshDataTest::mutableAccessNotAllowed() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, {}, indexData, indices, {}, vertexData, {positions}}; + CORRADE_COMPARE(data.indexDataFlags(), DataFlags{}); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlags{}); + + std::ostringstream out; + Error redirectError{&out}; + data.mutableIndexData(); + data.mutableVertexData(); + data.mutableIndices(); + data.mutableIndices(); + data.mutableAttribute(0); + data.mutableAttribute(0); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::mutableIndexData(): index data not mutable\n" + "Trade::MeshData::mutableVertexData(): vertex data not mutable\n" + "Trade::MeshData::mutableIndices(): index data not mutable\n" + "Trade::MeshData::mutableIndices(): index data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n"); +} + +void MeshDataTest::indicesNotIndexed() { + MeshData data{MeshPrimitive::Triangles, 37}; + + std::ostringstream out; + Error redirectError{&out}; + data.indexCount(); + data.indexType(); + data.indexOffset(); + data.indices(); + data.indices(); + data.indicesAsArray(); + UnsignedInt a[1]; + data.indicesInto(a); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::indexCount(): the mesh is not indexed\n" + "Trade::MeshData::indexType(): the mesh is not indexed\n" + "Trade::MeshData::indexOffset(): the mesh is not indexed\n" + "Trade::MeshData::indices(): the mesh is not indexed\n" + "Trade::MeshData::indices(): the mesh is not indexed\n" + "Trade::MeshData::indicesAsArray(): the mesh is not indexed\n" + "Trade::MeshData::indicesInto(): the mesh is not indexed\n"); +} + +void MeshDataTest::indicesWrongType() { + Containers::Array indexData{sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 57616; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}, 57617}; + + std::ostringstream out; + Error redirectError{&out}; + data.indices(); + CORRADE_COMPARE(out.str(), "Trade::MeshData::indices(): improper type requested for MeshIndexType::UnsignedShort\n"); +} + +void MeshDataTest::attributeNotFound() { + MeshAttributeData colors1{MeshAttribute::Color, VertexFormat::Vector3, nullptr}; + MeshAttributeData colors2{MeshAttribute::Color, VertexFormat::Vector4, nullptr}; + MeshData data{MeshPrimitive::Points, nullptr, {colors1, colors2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName(2); + data.attributeFormat(2); + data.attributeOffset(2); + data.attributeStride(2); + data.attributeArraySize(2); + data.attribute(2); + data.attribute(2); + data.attributeId(MeshAttribute::Position); + data.attributeId(MeshAttribute::Color, 2); + data.attributeFormat(MeshAttribute::Position); + data.attributeFormat(MeshAttribute::Color, 2); + data.attributeOffset(MeshAttribute::Position); + data.attributeOffset(MeshAttribute::Color, 2); + data.attributeStride(MeshAttribute::Position); + data.attributeStride(MeshAttribute::Color, 2); + data.attributeArraySize(MeshAttribute::Position); + data.attributeArraySize(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Color, 2); + data.positions2DAsArray(); + data.positions3DAsArray(); + data.normalsAsArray(); + data.textureCoordinates2DAsArray(); + data.colorsAsArray(2); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attributeName(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeFormat(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeOffset(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeId(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeId(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeFormat(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeFormat(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeArraySize(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes\n" + "Trade::MeshData::textureCoordinates2DInto(): index 0 out of range for 0 texture coordinate attributes\n" + "Trade::MeshData::colorsInto(): index 2 out of range for 2 color attributes\n"); +} + +void MeshDataTest::attributeWrongType() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + MeshData data{MeshPrimitive::Points, nullptr, {positions}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), "Trade::MeshData::attribute(): improper type requested for Trade::MeshAttribute::Position of format VertexFormat::Vector3\n"); +} + +void MeshDataTest::releaseIndexData() { + Containers::Array indexData{23}; + auto indexView = Containers::arrayCast(indexData.slice(6, 12)); + + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}, 10}; + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexOffset(), 6); + + Containers::Array released = data.releaseIndexData(); + CORRADE_COMPARE(static_cast(released.data() + 6), indexView.data()); + /* This is not null as we still need the value for calculating offsets */ + CORRADE_COMPARE(static_cast(data.indexData()), released.data()); + CORRADE_COMPARE(data.indexData().size(), 0); + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 0); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indexOffset(), 6); +} + +void MeshDataTest::releaseAttributeData() { + Containers::Array vertexData{16}; + auto vertexView = Containers::arrayCast(vertexData); + + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}}; + CORRADE_COMPARE(data.attributeCount(), 2); + + Containers::Array released = data.releaseAttributeData(); + CORRADE_COMPARE(released.size(), 2); + CORRADE_COMPARE(static_cast(released[0].data().data()), vertexView.data()); + CORRADE_COMPARE(released[0].data().size(), 2); + /* Unlike the other two, this is null as we don't need the value for + calculating anything */ + CORRADE_COMPARE(static_cast(data.attributeData()), nullptr); + CORRADE_COMPARE(data.attributeCount(), 0); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView); + CORRADE_COMPARE(data.vertexCount(), 2); +} + +void MeshDataTest::releaseVertexData() { + Containers::Array vertexData{80}; + auto vertexView = Containers::arrayCast(vertexData.slice(48, 72)); + + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}}; + CORRADE_COMPARE(data.attributeCount(), 2); + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeOffset(0), 48); + + Containers::Array released = data.releaseVertexData(); + CORRADE_VERIFY(data.attributeData()); + CORRADE_COMPARE(data.attributeCount(), 2); + CORRADE_COMPARE(static_cast(static_cast(data.attribute(0).data())), vertexView.data()); + CORRADE_COMPARE(static_cast(static_cast(data.mutableAttribute(0).data())), vertexView.data()); + /* Returned views should be patched to have zero size (but not the direct + access, there it stays as it's an internal API really) */ + CORRADE_COMPARE(data.attribute(0).size()[0], 0); + CORRADE_COMPARE(data.mutableAttribute(0).size()[0], 0); + CORRADE_COMPARE(data.attributeData()[0].data().size(), 3); + CORRADE_COMPARE(static_cast(released.data() + 48), vertexView.data()); + /* This is not null as we still need the value for calculating offsets */ + CORRADE_COMPARE(static_cast(data.vertexData()), released.data()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeOffset(0), 48); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshDataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 06c28ca496..d4df385bde 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -29,12 +29,12 @@ * @brief Forward declarations for the @ref Magnum::Trade namespace */ -#include "Magnum/Types.h" +#include + +#include "Magnum/Magnum.h" #ifdef MAGNUM_BUILD_DEPRECATED #include - -#include "Magnum/Magnum.h" #endif namespace Magnum { namespace Trade { @@ -59,14 +59,25 @@ class AnimationData; enum class CameraType: UnsignedByte; class CameraData; +enum class DataFlag: UnsignedByte; +typedef Containers::EnumSet DataFlags; + template class ImageData; typedef ImageData<1> ImageData1D; typedef ImageData<2> ImageData2D; typedef ImageData<3> ImageData3D; class LightData; -class MeshData2D; -class MeshData3D; + +enum class MeshAttribute: UnsignedShort; +class MeshIndexData; +class MeshAttributeData; +class MeshData; + +#ifdef MAGNUM_BUILD_DEPRECATED +class CORRADE_DEPRECATED("use MeshData instead") MeshData2D; +class CORRADE_DEPRECATED("use MeshData instead") MeshData3D; +#endif class MeshObjectData2D; class MeshObjectData3D; class ObjectData2D; diff --git a/src/Magnum/VertexFormat.cpp b/src/Magnum/VertexFormat.cpp new file mode 100644 index 0000000000..25789d6590 --- /dev/null +++ b/src/Magnum/VertexFormat.cpp @@ -0,0 +1,407 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +#include "VertexFormat.h" + +#include +#include +#include +#include + +namespace Magnum { + +UnsignedInt vertexFormatSize(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormatSize(): can't determine size of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + + switch(format) { + case VertexFormat::UnsignedByte: + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::Byte: + case VertexFormat::ByteNormalized: + return 1; + case VertexFormat::Half: + case VertexFormat::UnsignedShort: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::Short: + case VertexFormat::ShortNormalized: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector2b: + case VertexFormat::Vector2bNormalized: + return 2; + case VertexFormat::Vector3ub: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector3b: + case VertexFormat::Vector3bNormalized: + return 3; + case VertexFormat::Float: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + case VertexFormat::Vector2h: + case VertexFormat::Vector2us: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector2s: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4ubNormalized: + case VertexFormat::Vector4b: + case VertexFormat::Vector4bNormalized: + return 4; + case VertexFormat::Vector3h: + case VertexFormat::Vector3us: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector3s: + case VertexFormat::Vector3sNormalized: + return 6; + case VertexFormat::Double: + case VertexFormat::Vector2: + case VertexFormat::Vector2ui: + case VertexFormat::Vector2i: + case VertexFormat::Vector4h: + case VertexFormat::Vector4us: + case VertexFormat::Vector4usNormalized: + case VertexFormat::Vector4s: + case VertexFormat::Vector4sNormalized: + return 8; + case VertexFormat::Vector3: + case VertexFormat::Vector3ui: + case VertexFormat::Vector3i: + return 12; + case VertexFormat::Vector2d: + case VertexFormat::Vector4: + case VertexFormat::Vector4ui: + case VertexFormat::Vector4i: + return 16; + case VertexFormat::Vector3d: + return 24; + case VertexFormat::Vector4d: + return 32; + } + + CORRADE_ASSERT(false, "vertexFormatSize(): invalid format" << format, {}); +} + +UnsignedInt vertexFormatComponentCount(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormatComponentCount(): can't determine component count of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + + switch(format) { + case VertexFormat::Float: + case VertexFormat::Half: + case VertexFormat::Double: + case VertexFormat::UnsignedByte: + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::Byte: + case VertexFormat::ByteNormalized: + case VertexFormat::UnsignedShort: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::Short: + case VertexFormat::ShortNormalized: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + return 1; + + case VertexFormat::Vector2: + case VertexFormat::Vector2h: + case VertexFormat::Vector2d: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector2b: + case VertexFormat::Vector2bNormalized: + case VertexFormat::Vector2us: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector2s: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector2ui: + case VertexFormat::Vector2i: + return 2; + + case VertexFormat::Vector3: + case VertexFormat::Vector3h: + case VertexFormat::Vector3d: + case VertexFormat::Vector3ub: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector3b: + case VertexFormat::Vector3bNormalized: + case VertexFormat::Vector3us: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector3s: + case VertexFormat::Vector3sNormalized: + case VertexFormat::Vector3ui: + case VertexFormat::Vector3i: + return 3; + + case VertexFormat::Vector4: + case VertexFormat::Vector4h: + case VertexFormat::Vector4d: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4ubNormalized: + case VertexFormat::Vector4b: + case VertexFormat::Vector4bNormalized: + case VertexFormat::Vector4us: + case VertexFormat::Vector4usNormalized: + case VertexFormat::Vector4s: + case VertexFormat::Vector4sNormalized: + case VertexFormat::Vector4ui: + case VertexFormat::Vector4i: + return 4; + } + + CORRADE_ASSERT(false, "vertexFormatComponentCount(): invalid format" << format, {}); +} + +VertexFormat vertexFormatComponentFormat(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormatComponentFormat(): can't determine component format of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + + switch(format) { + case VertexFormat::Float: + case VertexFormat::Vector2: + case VertexFormat::Vector3: + case VertexFormat::Vector4: + return VertexFormat::Float; + + case VertexFormat::Half: + case VertexFormat::Vector2h: + case VertexFormat::Vector3h: + case VertexFormat::Vector4h: + return VertexFormat::Half; + + case VertexFormat::Double: + case VertexFormat::Vector2d: + case VertexFormat::Vector3d: + case VertexFormat::Vector4d: + return VertexFormat::Double; + + case VertexFormat::UnsignedByte: + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector3ub: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4ubNormalized: + return VertexFormat::UnsignedByte; + + case VertexFormat::Byte: + case VertexFormat::ByteNormalized: + case VertexFormat::Vector2b: + case VertexFormat::Vector2bNormalized: + case VertexFormat::Vector3b: + case VertexFormat::Vector3bNormalized: + case VertexFormat::Vector4b: + case VertexFormat::Vector4bNormalized: + return VertexFormat::Byte; + + case VertexFormat::UnsignedShort: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::Vector2us: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector3us: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector4us: + case VertexFormat::Vector4usNormalized: + return VertexFormat::UnsignedShort; + + case VertexFormat::Short: + case VertexFormat::ShortNormalized: + case VertexFormat::Vector2s: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector3s: + case VertexFormat::Vector3sNormalized: + case VertexFormat::Vector4s: + case VertexFormat::Vector4sNormalized: + return VertexFormat::Short; + + case VertexFormat::UnsignedInt: + case VertexFormat::Vector2ui: + case VertexFormat::Vector3ui: + case VertexFormat::Vector4ui: + return VertexFormat::UnsignedInt; + + case VertexFormat::Int: + case VertexFormat::Vector2i: + case VertexFormat::Vector3i: + case VertexFormat::Vector4i: + return VertexFormat::Int; + } + + CORRADE_ASSERT(false, "vertexFormatComponentType(): invalid format" << format, {}); +} + +bool isVertexFormatNormalized(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "isVertexFormatNormalized(): can't determine normalization of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + + switch(format) { + case VertexFormat::Float: + case VertexFormat::Half: + case VertexFormat::Double: + case VertexFormat::UnsignedByte: + case VertexFormat::Byte: + case VertexFormat::UnsignedShort: + case VertexFormat::Short: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + case VertexFormat::Vector2: + case VertexFormat::Vector2h: + case VertexFormat::Vector2d: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2b: + case VertexFormat::Vector2us: + case VertexFormat::Vector2s: + case VertexFormat::Vector2ui: + case VertexFormat::Vector2i: + case VertexFormat::Vector3: + case VertexFormat::Vector3h: + case VertexFormat::Vector3d: + case VertexFormat::Vector3ub: + case VertexFormat::Vector3b: + case VertexFormat::Vector3us: + case VertexFormat::Vector3s: + case VertexFormat::Vector3ui: + case VertexFormat::Vector3i: + case VertexFormat::Vector4: + case VertexFormat::Vector4h: + case VertexFormat::Vector4d: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4b: + case VertexFormat::Vector4us: + case VertexFormat::Vector4s: + case VertexFormat::Vector4ui: + case VertexFormat::Vector4i: + return false; + + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::ByteNormalized: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::ShortNormalized: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector2bNormalized: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector3bNormalized: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector3sNormalized: + case VertexFormat::Vector4ubNormalized: + case VertexFormat::Vector4bNormalized: + case VertexFormat::Vector4usNormalized: + case VertexFormat::Vector4sNormalized: + return true; + } + + CORRADE_ASSERT(false, "isVertexFormatNormalized(): invalid format" << format, {}); +} + +VertexFormat vertexFormat(const VertexFormat format, UnsignedInt componentCount, bool normalized) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormat(): can't assemble a format out of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + + VertexFormat componentFormat = vertexFormatComponentFormat(format); + + /* First turn the format into a normalized one, if requested */ + if(normalized) { + switch(componentFormat) { + case VertexFormat::UnsignedByte: + componentFormat = VertexFormat::UnsignedByteNormalized; + break; + case VertexFormat::Byte: + componentFormat = VertexFormat::ByteNormalized; + break; + case VertexFormat::UnsignedShort: + componentFormat = VertexFormat::UnsignedShortNormalized; + break; + case VertexFormat::Short: + componentFormat = VertexFormat::ShortNormalized; + break; + default: CORRADE_ASSERT(false, + "vertexFormat():" << format << "can't be made normalized", {}); + } + } + + /* Then turn them into desired component count, assuming the initial order + is the same in all cases */ + if(componentCount == 1) + return componentFormat; + else if(componentCount == 2) + return VertexFormat(UnsignedInt(VertexFormat::Vector2) + + UnsignedInt(componentFormat) - UnsignedInt(VertexFormat::Float)); + else if(componentCount == 3) + return VertexFormat(UnsignedInt(VertexFormat::Vector3) + + UnsignedInt(componentFormat) - UnsignedInt(VertexFormat::Float)); + else if(componentCount == 4) + return VertexFormat(UnsignedInt(VertexFormat::Vector4) + + UnsignedInt(componentFormat) - UnsignedInt(VertexFormat::Float)); + else CORRADE_ASSERT(false, + "vertexFormat(): invalid component count" << componentCount, {}); + + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +namespace { + +constexpr const char* VertexFormatNames[] { + #define _c(format) #format, + #include "Magnum/Implementation/vertexFormatMapping.hpp" + #undef _c +}; + +} + +Debug& operator<<(Debug& debug, const VertexFormat value) { + debug << "VertexFormat" << Debug::nospace; + + if(isVertexFormatImplementationSpecific(value)) { + return debug << "::ImplementationSpecific(" << Debug::nospace << reinterpret_cast(vertexFormatUnwrap(value)) << Debug::nospace << ")"; + } + + if(UnsignedInt(value) - 1 < Containers::arraySize(VertexFormatNames)) { + return debug << "::" << Debug::nospace << VertexFormatNames[UnsignedInt(value) - 1]; + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; +} + +} + +namespace Corrade { namespace Utility { + +std::string ConfigurationValue::toString(Magnum::VertexFormat value, ConfigurationValueFlags) { + if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::VertexFormatNames)) + return Magnum::VertexFormatNames[Magnum::UnsignedInt(value) - 1]; + + return {}; +} + +Magnum::VertexFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { + for(std::size_t i = 0; i != Containers::arraySize(Magnum::VertexFormatNames); ++i) + if(stringValue == Magnum::VertexFormatNames[i]) return Magnum::VertexFormat(i + 1); + + return {}; +} + +}} diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h new file mode 100644 index 0000000000..60198f2a6c --- /dev/null +++ b/src/Magnum/VertexFormat.h @@ -0,0 +1,917 @@ +#ifndef Magnum_VertexFormat_h +#define Magnum_VertexFormat_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Enum @ref Magnum::VertexFormat, function @ref Magnum::isVertexFormatImplementationSpecific(), @ref Magnum::vertexFormatWrap(), @ref Magnum::vertexFormatUnwrap(), @ref Magnum::vertexFormatSize(), @ref Magnum::vertexFormatComponentCount(), @ref Magnum::vertexFormatComponentFormat(), @ref Magnum::isVertexFormatNormalized() + */ + +#include +#include + +#include "Magnum/Magnum.h" +#include "Magnum/visibility.h" + +namespace Magnum { + +/** +@brief Vertex format +@m_since_latest + +Like @ref PixelFormat, but for mesh attributes --- including double-precision +types and matrices. Can act also as a wrapper for implementation-specific mesh +attribute type values using @ref vertexFormatWrap() and +@ref vertexFormatUnwrap(). Distinction between generic and +implementation-specific types can be done using +@ref isVertexFormatImplementationSpecific(). + +In case of OpenGL, corresponds to a tuple of @ref GL::DynamicAttribute::Kind, +@ref GL::DynamicAttribute::Components and @ref GL::DynamicAttribute::DataType +and is convertible to them using +@ref GL::DynamicAttribute::DynamicAttribute(Kind, UnsignedInt, VertexFormat). +See documentation of each value for more information about the mapping. Note +that not every format is available on all targets, use +@ref GL::hasVertexFormat() to check for its presence. + +In case of Vulkan, corresponds to @type_vk_keyword{Format} and is convertible +to it using @ref Vk::vkFormat(Magnum::VertexFormat). See documentation of each +value for more information about the mapping. Note that not every format may be +available, use @ref Vk::hasVkFormat(Magnum::VertexFormat) to check for its +presence. + +For D3D, corresponds to @m_class{m-doc-external} [DXGI_FORMAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format); +for Metal, corresponds to @m_class{m-doc-external} [MTLVertexFormat](https://developer.apple.com/documentation/metal/mtlvertexformat?language=objc). +See documentation of each value for more information about the mapping. +@see @ref Trade::MeshData, @ref Trade::MeshAttributeData, + @ref Trade::MeshAttribute +*/ +enum class VertexFormat: UnsignedInt { + /* Zero reserved for an invalid type (but not being a named value) */ + + /** + * @ref Float. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat?language=objc). + * @m_keywords{DXGI_FORMAT_R32_FLOAT MTLVertexFormatFloat} + */ + Float = 1, + + /** + * @ref Half. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf?language=objc). + * @m_keywords{DXGI_FORMAT_R16_FLOAT MTLVertexFormatHalf} + */ + Half, + + /** + * @ref Double. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64_SFLOAT,Format}. No D3D or Metal equivalent. + */ + Double, + + /** + * @ref UnsignedByte. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar?language=objc) + * @m_keywords{DXGI_FORMAT_R8_UINT MTLVertexFormatUChar} + */ + UnsignedByte, + + /** + * @ref UnsignedByte, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUCharNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatucharnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8_UNORM MTLVertexFormatUCharNormalized} + */ + UnsignedByteNormalized, + + /** + * @ref Byte. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar?language=objc). + * @m_keywords{DXGI_FORMAT_R8_SINT MTLVertexFormatChar} + */ + Byte, + + /** + * @ref Byte, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatCharNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatcharnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8_SNORM MTLVertexFormatCharNormalized} + */ + ByteNormalized, + + /** + * @ref UnsignedShort. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort?language=objc). + * @m_keywords{DXGI_FORMAT_R16_UINT MTLVertexFormatUShort} + */ + UnsignedShort, + + /** + * @ref UnsignedShort, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShortNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushortnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16_UNORM MTLVertexFormatUShortNormalized} + */ + UnsignedShortNormalized, + + /** + * @ref Short. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort?language=objc). + * @m_keywords{DXGI_FORMAT_R16_SINT MTLVertexFormatShort} + */ + Short, + + /** + * @ref Short, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShortNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshortnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16_SNORM MTLVertexFormatShortNormalized} + */ + ShortNormalized, + + /** + * @ref UnsignedInt. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint?language=objc). + * @m_keywords{DXGI_FORMAT_R32_UINT MTLVertexFormatUInt} + */ + UnsignedInt, + + /** + * @ref Int. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint?language=objc). + * @m_keywords{DXGI_FORMAT_R32_SINT MTLVertexFormatInt} + */ + Int, + + /** + * @ref Vector2. Usually used for 2D positions and 2D texture coordinates. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32G32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat2?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32_FLOAT MTLVertexFormatFloat2} + */ + Vector2, + + /** + * @ref Vector2h. Can be used instead of @ref VertexFormat::Vector2 for 2D + * positions and 2D texture coordinates. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16G16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf2?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_FLOAT MTLVertexFormatHalf2} + */ + Vector2h, + + /** + * @ref Vector2d. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64G64_SFLOAT,Format}. No D3D or Metal + * equivalent. + */ + Vector2d, + + /** + * @ref Vector2ub. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [0, 255] @f$ is interpreted as @f$ [0.0, 255.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar2?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_UINT MTLVertexFormatUChar2} + */ + Vector2ub, + + /** + * @ref Vector2ub, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_UNORM MTLVertexFormatUChar2Normalized} + */ + Vector2ubNormalized, + + /** + * @ref Vector2b. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [-128, 127] @f$ is interpreted as @f$ [-128.0, 127.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar2?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_SINT MTLVertexFormatChar2} + */ + Vector2b, + + /** + * @ref Vector2b, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_SNORM MTLVertexFormatChar2Normalized} + */ + Vector2bNormalized, + + /** + * @ref Vector2us. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [0, 65535] @f$ is interpreted as @f$ [0.0, 65535.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16G16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort2?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_UINT MTLVertexFormatUShort2} + */ + Vector2us, + + /** + * @ref Vector2us, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16G16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_UNORM MTLVertexFormatUShort2Normalized} + */ + Vector2usNormalized, + + /** + * @ref Vector2s. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [-32768, 32767] @f$ is interpreted as @f$ [-32768.0, 32767.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort2?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_SINT MTLVertexFormatShort2} + */ + Vector2s, + + /** + * @ref Vector2s, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_SNORM MTLVertexFormatShort2Normalized} + */ + Vector2sNormalized, + + /** + * @ref Vector2ui. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32G32_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint2?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32_UINT MTLVertexFormatUInt2} + */ + Vector2ui, + + /** + * @ref Vector2i. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32G32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint2?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32_SINT MTLVertexFormatInt2} + */ + Vector2i, + + /** + * @ref Vector3 or @ref Color3. Usually used for 3D positions, normals and + * three-component colors. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32G32B32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat3?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32_FLOAT MTLVertexFormatFloat3} + */ + Vector3, + + /** + * @ref Vector3h. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions and three-component colors. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16G16B16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf3?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_FLOAT MTLVertexFormatHalf3} + */ + Vector3h, + + /** + * @ref Vector3d. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64G64B64_SFLOAT,Format}. No D3D or Metal + * equivalent. + */ + Vector3d, + + /** + * @ref Vector3ub. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions, in which case the range @f$ [0, 255] @f$ is + * interpreted as @f$ [0.0, 255.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar3?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_UINT MTLVertexFormatUChar3} + */ + Vector3ub, + + /** + * @ref Vector3ub, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector3 + * for packed 3D positions and three-component colors. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_UNORM MTLVertexFormatUChar3Normalized} + */ + Vector3ubNormalized, + + /** + * @ref Vector3b. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions, in which case the range @f$ [-128, 127] @f$ is + * interpreted as @f$ [-128.0, 127.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar3?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_SINT MTLVertexFormatChar3} + */ + Vector3b, + + /** + * @ref Vector3b, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of + * @ref VertexFormat::Vector3 for packed 3D positions and normals. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_SNORM MTLVertexFormatChar3Normalized} + */ + Vector3bNormalized, + + /** + * @ref Vector3us. Can be used instead of @ref VertexFormat::Vector3 for + * packed 2D positions, in which case the range @f$ [0, 65535] @f$ is + * interpreted as @f$ [0.0, 65535.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16G16B16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort3?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_UINT MTLVertexFormatUShort3} + */ + Vector3us, + + /** + * @ref Vector3us, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 3D positions and three-component colors. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16G16B16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_UNORM MTLVertexFormatUShort3Normalized} + */ + Vector3usNormalized, + + /** + * @ref Vector3s. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions, in which case the range @f$ [-32768, 32767] @f$ is + * interpreted as @f$ [-32768.0, 32767.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort3?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_SINT MTLVertexFormatShort3} + */ + Vector3s, + + /** + * @ref Vector3s, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector3 + * for packed 3D positions and normals. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_SNORM MTLVertexFormatShort3Normalized} + */ + Vector3sNormalized, + + /** + * @ref Vector3ui. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32G32B32_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint3?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32_UINT MTLVertexFormatUInt3} + */ + Vector3ui, + + /** + * @ref Vector3i. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32G32B32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint3?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32_SINT MTLVertexFormatInt3} + */ + Vector3i, + + /** + * @ref Vector4 or @ref Color4. Usually used for four-component colors. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32G32B32A32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32A32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat4?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32A32_FLOAT MTLVertexFormatFloat4} + */ + Vector4, + + /** + * @ref Vector4h. Can be used instead of @ref VertexFormat::Vector4 for + * four-component colors. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16G16B16A16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf4?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_FLOAT MTLVertexFormatHalf4} + */ + Vector4h, + + /** + * @ref Vector4d. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64G64B64A64_SFLOAT,Format}. No D3D or Metal + * equivalent. + */ + Vector4d, + + /** + * @ref Vector4ub. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8A8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar4?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_UINT MTLVertexFormatUChar4} + */ + Vector4ub, + + /** + * @ref Vector4ub, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector4 + * for packed linear four-component colors. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8A8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_UNORM MTLVertexFormatUChar4Normalized} + */ + Vector4ubNormalized, + + /** + * @ref Vector4b. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8A8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar4?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_SINT MTLVertexFormatChar4} + */ + Vector4b, + + /** + * @ref Vector4b, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8A8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_SNORM MTLVertexFormatChar4Normalized} + */ + Vector4bNormalized, + + /** + * @ref Vector4us. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16G16B16A16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort4?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_UINT MTLVertexFormatUShort4} + */ + Vector4us, + + /** + * @ref Vector4us, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector4 + * for packed linear four-component colors. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16G16B16A16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_UNORM MTLVertexFormatUShort4Normalized} + */ + Vector4usNormalized, + + /** + * @ref Vector4s. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16A16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort4?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_SINT MTLVertexFormatShort4} + */ + Vector4s, + + /** + * @ref Vector4s, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16A16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_SNORM MTLVertexFormatShort4Normalized} + */ + Vector4sNormalized, + + /** + * @ref Vector4ui. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32G32B32A16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32A32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint4?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32A32_UINT MTLVertexFormatUInt4} + */ + Vector4ui, + + /** + * @ref Vector4i. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32G32B32A32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32A32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint4?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32A32_SINT MTLVertexFormatInt4} + */ + Vector4i +}; + +/** +@debugoperatorenum{VertexFormat} +@m_since_latest +*/ +MAGNUM_EXPORT Debug& operator<<(Debug& debug, VertexFormat value); + +/** +@brief Whether a @ref VertexFormat value wraps an implementation-specific identifier +@m_since_latest + +Returns @cpp true @ce if value of @p format has its highest bit set, +@cpp false @ce otherwise. Use @ref vertexFormatWrap() and @ref vertexFormatUnwrap() +to wrap/unwrap an implementation-specific indentifier to/from +@ref VertexFormat. +*/ +constexpr bool isVertexFormatImplementationSpecific(VertexFormat format) { + return UnsignedInt(format) & (1u << 31); +} + +/** +@brief Wrap an implementation-specific vertex format identifier in @ref VertexFormat +@m_since_latest + +Sets the highest bit on @p type to mark it as implementation-specific. Expects +that @p type fits into the remaining bits. Use @ref vertexFormatUnwrap() +for the inverse operation. +@see @ref isVertexFormatImplementationSpecific() +*/ +template constexpr VertexFormat vertexFormatWrap(T implementationSpecific) { + static_assert(sizeof(T) <= 4, "types larger than 32bits are not supported"); + return CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(implementationSpecific) & (1u << 31)), + "vertexFormatWrap(): implementation-specific value" << reinterpret_cast(implementationSpecific) << "already wrapped or too large"), + VertexFormat((1u << 31)|UnsignedInt(implementationSpecific)); +} + +/** +@brief Unwrap an implementation-specific vertex format identifier from @ref VertexFormat +@m_since_latest + +Unsets the highest bit from @p type to extract the implementation-specific +value. Expects that @p type has it set. Use @ref vertexFormatWrap() for +the inverse operation. +@see @ref isVertexFormatImplementationSpecific() +*/ +template constexpr T vertexFormatUnwrap(VertexFormat format) { + return CORRADE_CONSTEXPR_ASSERT(UnsignedInt(format) & (1u << 31), + "vertexFormatUnwrap():" << format << "isn't a wrapped implementation-specific value"), + T(UnsignedInt(format) & ~(1u << 31)); +} + +/** +@brief Size of given vertex format +@m_since_latest + +To get size of a single component, call this function on a result of +@ref vertexFormatComponentFormat(). +*/ +MAGNUM_EXPORT UnsignedInt vertexFormatSize(VertexFormat format); + +/** +@brief Component format of given vertex format +@m_since_latest + +The function also removes the normalization aspect from the type --- use +@ref isVertexFormatNormalized() to query that. Returns for example +@ref VertexFormat::Short for @ref VertexFormat::ShortNormalized or +@ref VertexFormat::UnsignedByte for @ref VertexFormat::Vector3ub. +Calling @ref vertexFormatComponentCount() on the return value will always +give @cpp 1 @ce; calling @ref isVertexFormatNormalized() on the return +value will always give @cpp false @ce. +@see @ref vertexFormat(VertexFormat, UnsignedInt, bool) +*/ +MAGNUM_EXPORT VertexFormat vertexFormatComponentFormat(VertexFormat format); + +/** +@brief Component count of given vertex format +@m_since_latest + +Returns @cpp 1 @ce for scalar types and e.g. @cpp 3 @ce for +@ref VertexFormat::Vector3ub. +@see @ref vertexFormat(VertexFormat, UnsignedInt, bool) +*/ +MAGNUM_EXPORT UnsignedInt vertexFormatComponentCount(VertexFormat format); + +/** +@brief Component count of given vertex format +@m_since_latest + +Returns @cpp true @ce for `*Normalized` types, @cpp false @ce otherwise. In +particular, floating-point types are *not* treated as normalized, even though +for example colors might commonly have values only in the @f$ [0.0, 1.0] @f$ +range (or normals in the @f$ [-1.0, 1.0] @f$ range). +@see @ref vertexFormat(VertexFormat, UnsignedInt, bool) +*/ +MAGNUM_EXPORT bool isVertexFormatNormalized(VertexFormat format); + +/** +@brief Assemble a vertex format from parts +@m_since_latest + +Converts @p format to a new format of desired component count and +normalization. Expects that @p componentCount is not larger than @cpp 4 @ce and +@p normalized is @cpp true @ce only for 8- and 16-byte integer types. +@see @ref vertexFormatComponentFormat(), + @ref vertexFormatComponentCount(), + @ref isVertexFormatNormalized() +*/ +MAGNUM_EXPORT VertexFormat vertexFormat(VertexFormat format, UnsignedInt componentCount, bool normalized); + +} + +namespace Corrade { namespace Utility { + +/** +@configurationvalue{Magnum::VertexFormat} +@m_since_latest +*/ +template<> struct MAGNUM_EXPORT ConfigurationValue { + ConfigurationValue() = delete; + + /** + * @brief Write enum value as string + * + * If the value is invalid, returns empty string. + */ + static std::string toString(Magnum::VertexFormat value, ConfigurationValueFlags); + + /** + * @brief Read enum value as string + * + * If the value is invalid, returns a zero (invalid) type. + */ + static Magnum::VertexFormat fromString(const std::string& stringValue, ConfigurationValueFlags); +}; + +}} + +#endif diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index a2dcf4490d..dc21ac4ba0 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -42,7 +42,9 @@ set(MagnumVk_HEADERS visibility.h) set(MagnumVk_PRIVATE_HEADERS - Implementation/FormatMapping.hpp) + Implementation/compressedPixelFormatMapping.hpp + Implementation/meshAttributeTypeMapping.hpp + Implementation/pixelFormatMapping.hpp) # Objects shared between main and test library # add_library(MagnumVkObjects OBJECT diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index 75844d2380..edfdff101a 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -30,6 +30,7 @@ #include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" #include "Magnum/Sampler.h" +#include "Magnum/VertexFormat.h" namespace Magnum { namespace Vk { @@ -42,7 +43,10 @@ constexpr VkPrimitiveTopology PrimitiveTopologyMapping[]{ VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + VkPrimitiveTopology(~UnsignedInt{}), /* Instances */ + VkPrimitiveTopology(~UnsignedInt{}), /* Faces */ + VkPrimitiveTopology(~UnsignedInt{}) /* Edges */ }; constexpr VkIndexType IndexTypeMapping[]{ @@ -54,6 +58,14 @@ constexpr VkIndexType IndexTypeMapping[]{ #ifndef DOXYGEN_GENERATING_OUTPUT /* It gets *really* confused */ static_assert(VK_FORMAT_UNDEFINED == 0, "VK_FORMAT_UNDEFINED is assumed to be 0"); +constexpr VkFormat VertexFormatMapping[] { + #define _c(input, format) VK_FORMAT_ ## format, + #define _s(input) {}, + #include "Magnum/Vk/Implementation/vertexFormatMapping.hpp" + #undef _s + #undef _c +}; + constexpr VkFormat PixelFormatMapping[] { #define _c(input, format) VK_FORMAT_ ## format, #define _s(input) {}, @@ -93,35 +105,50 @@ constexpr VkSamplerAddressMode SamplerAddressModeMapping[]{ } bool hasVkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { - CORRADE_ASSERT(UnsignedInt(primitive) < Containers::arraySize(PrimitiveTopologyMapping), + if(isMeshPrimitiveImplementationSpecific(primitive)) + return true; + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), "Vk::hasVkPrimitiveTopology(): invalid primitive" << primitive, {}); - return UnsignedInt(PrimitiveTopologyMapping[UnsignedInt(primitive)]) != ~UnsignedInt{}; + return UnsignedInt(PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; } VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { - CORRADE_ASSERT(UnsignedInt(primitive) < Containers::arraySize(PrimitiveTopologyMapping), + if(isMeshPrimitiveImplementationSpecific(primitive)) + return meshPrimitiveUnwrap(primitive); + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), "Vk::vkPrimitiveTopology(): invalid primitive" << primitive, {}); - const VkPrimitiveTopology out = PrimitiveTopologyMapping[UnsignedInt(primitive)]; + const VkPrimitiveTopology out = PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]; CORRADE_ASSERT(out != VkPrimitiveTopology(~UnsignedInt{}), "Vk::vkPrimitiveTopology(): unsupported primitive" << primitive, {}); return out; } bool hasVkIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) < Containers::arraySize(IndexTypeMapping), + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), "Vk::hasVkIndexType(): invalid type" << type, {}); - return UnsignedInt(IndexTypeMapping[UnsignedInt(type)]) != ~UnsignedInt{}; + return UnsignedInt(IndexTypeMapping[UnsignedInt(type) - 1]) != ~UnsignedInt{}; } VkIndexType vkIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) < Containers::arraySize(IndexTypeMapping), + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), "Vk::vkIndexType(): invalid type" << type, {}); - const VkIndexType out = IndexTypeMapping[UnsignedInt(type)]; + const VkIndexType out = IndexTypeMapping[UnsignedInt(type) - 1]; CORRADE_ASSERT(out != VkIndexType(~UnsignedInt{}), "Vk::vkIndexType(): unsupported type" << type, {}); return out; } +bool hasVkFormat(const Magnum::VertexFormat format) { + if(isVertexFormatImplementationSpecific(format)) + return true; + + CORRADE_ASSERT(UnsignedInt(format) - 1 < Containers::arraySize(VertexFormatMapping), + "Vk::hasVkFormat(): invalid format" << format, {}); + return UnsignedInt(VertexFormatMapping[UnsignedInt(format) - 1]); +} + bool hasVkFormat(const Magnum::PixelFormat format) { if(isPixelFormatImplementationSpecific(format)) return true; @@ -140,6 +167,18 @@ bool hasVkFormat(const Magnum::CompressedPixelFormat format) { return UnsignedInt(CompressedPixelFormatMapping[UnsignedInt(format) - 1]); } +VkFormat vkFormat(const Magnum::VertexFormat format) { + if(isVertexFormatImplementationSpecific(format)) + return vertexFormatUnwrap(format); + + CORRADE_ASSERT(UnsignedInt(format) - 1 < Containers::arraySize(VertexFormatMapping), + "Vk::vkFormat(): invalid format" << format, {}); + const VkFormat out = VertexFormatMapping[UnsignedInt(format) - 1]; + CORRADE_ASSERT(UnsignedInt(out), + "Vk::vkFormat(): unsupported format" << format, {}); + return out; +} + VkFormat vkFormat(const Magnum::PixelFormat format) { if(isPixelFormatImplementationSpecific(format)) return pixelFormatUnwrap(format); diff --git a/src/Magnum/Vk/Enums.h b/src/Magnum/Vk/Enums.h index 3c44b60fbe..230fd0aaea 100644 --- a/src/Magnum/Vk/Enums.h +++ b/src/Magnum/Vk/Enums.h @@ -40,7 +40,9 @@ namespace Magnum { namespace Vk { In particular, Vulkan doesn't support the @ref MeshPrimitive::LineLoop primitive. Returns @cpp false @ce if Vulkan doesn't support such primitive, -@cpp true @ce otherwise. The @p primitive value is expected to be valid. +@cpp true @ce otherwise. Moreover, returns @cpp true @ce also for all types +that are @ref isMeshPrimitiveImplementationSpecific(). The @p primitive value +is expected to be valid. @see @ref vkPrimitiveTopology() */ MAGNUM_VK_EXPORT bool hasVkPrimitiveTopology(Magnum::MeshPrimitive primitive); @@ -48,6 +50,12 @@ MAGNUM_VK_EXPORT bool hasVkPrimitiveTopology(Magnum::MeshPrimitive primitive); /** @brief Convert generic mesh primitive to Vulkan primitive topology +In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for +@p primitive, maps it to a corresponding Vulkan primitive topology. In case +@ref isMeshPrimitiveImplementationSpecific() returns @cpp true @ce, assumes +@p primitive stores a Vulkan-specific primitive topology and returns +@ref meshPrimitiveUnwrap() cast to @type_vk{VkPrimitiveTopology}. + Not all generic mesh primitives are available in Vulkan and this function expects that given primitive is available. Use @ref hasVkPrimitiveTopology() to query availability of given primitive. @@ -79,6 +87,24 @@ of given index type. */ MAGNUM_VK_EXPORT VkIndexType vkIndexType(Magnum::MeshIndexType type); +/** +@brief Check availability of a generic vertex format +@m_since_latest + +Some Vulkan targets don't support all generic vertex formats. Returns +@cpp false @ce if current target can't support such format, @cpp true @ce +otherwise. Moreover, returns @cpp true @ce also for all formats that are +@ref isVertexFormatImplementationSpecific(). The @p format value is expected +to be valid. + +@note Support of some formats depends on presence of a particular Vulkan + extension. Such check is outside of the scope of this function and you are + expected to verify extension availability before using such format. + +@see @ref vkFormat(Magnum::VertexFormat) +*/ +MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::VertexFormat format); + /** @brief Check availability of a generic pixel format @@ -92,7 +118,7 @@ be valid. extension. Such check is outside of the scope of this function and you are expected to verify extension availability before using such format. -@see @ref vkFormat() +@see @ref vkFormat(Magnum::PixelFormat) */ MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::PixelFormat format); @@ -109,22 +135,38 @@ expected to be valid. extension. Such check is outside of the scope of this function and you are expected to verify extension availability before using such format. -@see @ref vkFormat() +@see @ref vkFormat(Magnum::CompressedPixelFormat) */ MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::CompressedPixelFormat format); +/** +@brief Convert a generic vertex format to Vulkan format +@m_since_latest + +In case @ref isVertexFormatImplementationSpecific() returns @cpp false @ce for +@p format, maps it to a corresponding Vulkan format. In case +@ref isVertexFormatImplementationSpecific() returns @cpp true @ce, assumes +@p format stores a Vulkan-specific format and returns @ref vertexFormatUnwrap() +cast to @type_vk{Format}. + +Not all generic vertex formats may be available on all targets and this +function expects that given format is available on the target. Use +@ref hasVkFormat(Magnum::VertexFormat) to query availability of given format. +*/ +MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::VertexFormat format); + /** @brief Convert a generic pixel format to Vulkan format In case @ref isPixelFormatImplementationSpecific() returns @cpp false @ce for @p format, maps it to a corresponding Vulkan format. In case @ref isPixelFormatImplementationSpecific() returns @cpp true @ce, assumes -@p format stores Vulkan-specific format and returns @ref pixelFormatUnwrap() +@p format stores a Vulkan-specific format and returns @ref pixelFormatUnwrap() cast to @type_vk{Format}. Not all generic pixel formats may be available on all targets and this function -expects that given format is available on the target. Use @ref hasVkFormat() to -query availability of given format. +expects that given format is available on the target. Use +@ref hasVkFormat(Magnum::PixelFormat) to query availability of given format. */ MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::PixelFormat format); @@ -134,12 +176,13 @@ MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::PixelFormat format); In case @ref isCompressedPixelFormatImplementationSpecific() returns @cpp false @ce for @p format, maps it to a corresponding Vulkan format. In case @ref isCompressedPixelFormatImplementationSpecific() returns @cpp true @ce, -assumes @p format stores Vulkan-specific format and returns +assumes @p format stores a Vulkan-specific format and returns @ref compressedPixelFormatUnwrap() cast to @type_vk{Format}. Not all generic pixel formats may be available on all targets and this function -expects that given format is available on the target. Use @ref hasVkFormat() to -query availability of given format. +expects that given format is available on the target. Use +@ref hasVkFormat(Magnum::CompressedPixelFormat) to query availability of given +format. */ MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::CompressedPixelFormat format); diff --git a/src/Magnum/Vk/Implementation/vertexFormatMapping.hpp b/src/Magnum/Vk/Implementation/vertexFormatMapping.hpp new file mode 100644 index 0000000000..2efe832bc6 --- /dev/null +++ b/src/Magnum/Vk/Implementation/vertexFormatMapping.hpp @@ -0,0 +1,80 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/* See Magnum/Vk/Enums.cpp and Magnum/Vk/Test/EnumsTest.cpp */ +#ifdef _c +_c(Float, R32_SFLOAT) +_c(Half, R16_SFLOAT) +_c(Double, R64_SFLOAT) +_c(UnsignedByte, R8_UINT) +_c(UnsignedByteNormalized, R8_UNORM) +_c(Byte, R8_SINT) +_c(ByteNormalized, R8_SNORM) +_c(UnsignedShort, R16_UINT) +_c(UnsignedShortNormalized, R16_UNORM) +_c(Short, R16_SINT) +_c(ShortNormalized, R16_SNORM) +_c(UnsignedInt, R32_UINT) +_c(Int, R32_SINT) +_c(Vector2, R32G32_SFLOAT) +_c(Vector2h, R16G16_SFLOAT) +_c(Vector2d, R64G64_SFLOAT) +_c(Vector2ub, R8G8_UINT) +_c(Vector2ubNormalized, R8G8_UNORM) +_c(Vector2b, R8G8_SINT) +_c(Vector2bNormalized, R8G8_SNORM) +_c(Vector2us, R16G16_UINT) +_c(Vector2usNormalized, R16G16_UNORM) +_c(Vector2s, R16G16_SINT) +_c(Vector2sNormalized, R16G16_SNORM) +_c(Vector2ui, R32G32_UINT) +_c(Vector2i, R32G32_SINT) +_c(Vector3, R32G32B32_SFLOAT) +_c(Vector3h, R16G16B16_SFLOAT) +_c(Vector3d, R64G64B64_SFLOAT) +_c(Vector3ub, R8G8B8_UINT) +_c(Vector3ubNormalized, R8G8B8_UNORM) +_c(Vector3b, R8G8B8_SINT) +_c(Vector3bNormalized, R8G8B8_SNORM) +_c(Vector3us, R16G16B16_UINT) +_c(Vector3usNormalized, R16G16B16_UNORM) +_c(Vector3s, R16G16B16_SINT) +_c(Vector3sNormalized, R16G16B16_SNORM) +_c(Vector3ui, R32G32B32_UINT) +_c(Vector3i, R32G32B32_SINT) +_c(Vector4, R32G32B32A32_SFLOAT) +_c(Vector4h, R16G16B16A16_SFLOAT) +_c(Vector4d, R64G64B64A64_SFLOAT) +_c(Vector4ub, R8G8B8A8_UINT) +_c(Vector4ubNormalized, R8G8B8A8_UNORM) +_c(Vector4b, R8G8B8A8_SINT) +_c(Vector4bNormalized, R8G8B8A8_SNORM) +_c(Vector4us, R16G16B16A16_UINT) +_c(Vector4usNormalized, R16G16B16A16_UNORM) +_c(Vector4s, R16G16B16A16_SINT) +_c(Vector4sNormalized, R16G16B16A16_SNORM) +_c(Vector4ui, R32G32B32A32_UINT) +_c(Vector4i, R32G32B32A32_SINT) +#endif diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index 3671f6638a..ee33252f9b 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -30,6 +30,7 @@ #include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" #include "Magnum/Sampler.h" +#include "Magnum/VertexFormat.h" #include "Magnum/Vk/Enums.h" namespace Magnum { namespace Vk { namespace Test { namespace { @@ -38,6 +39,7 @@ struct EnumsTest: TestSuite::Tester { explicit EnumsTest(); void mapVkPrimitiveTopology(); + void mapVkPrimitiveTopologyImplementationSpecific(); void mapVkPrimitiveTopologyUnsupported(); void mapVkPrimitiveTopologyInvalid(); @@ -45,6 +47,11 @@ struct EnumsTest: TestSuite::Tester { void mapVkIndexTypeUnsupported(); void mapVkIndexTypeInvalid(); + void mapVkFormatVertexFormat(); + void mapVkFormatVertexFormatImplementationSpecific(); + void mapVkFormatVertexFormatUnsupported(); + void mapVkFormatVertexFormatInvalid(); + void mapVkFormatPixelFormat(); void mapVkFormatPixelFormatImplementationSpecific(); void mapVkFormatPixelFormatUnsupported(); @@ -69,6 +76,7 @@ struct EnumsTest: TestSuite::Tester { EnumsTest::EnumsTest() { addTests({&EnumsTest::mapVkPrimitiveTopology, + &EnumsTest::mapVkPrimitiveTopologyImplementationSpecific, &EnumsTest::mapVkPrimitiveTopologyUnsupported, &EnumsTest::mapVkPrimitiveTopologyInvalid, @@ -76,6 +84,11 @@ EnumsTest::EnumsTest() { &EnumsTest::mapVkIndexTypeUnsupported, &EnumsTest::mapVkIndexTypeInvalid, + &EnumsTest::mapVkFormatVertexFormat, + &EnumsTest::mapVkFormatVertexFormatImplementationSpecific, + &EnumsTest::mapVkFormatVertexFormatUnsupported, + &EnumsTest::mapVkFormatVertexFormatInvalid, + &EnumsTest::mapVkFormatPixelFormat, &EnumsTest::mapVkFormatPixelFormatImplementationSpecific, &EnumsTest::mapVkFormatPixelFormatUnsupported, @@ -141,6 +154,12 @@ void EnumsTest::mapVkPrimitiveTopology() { } } +void EnumsTest::mapVkPrimitiveTopologyImplementationSpecific() { + CORRADE_VERIFY(hasVkPrimitiveTopology(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY))); + CORRADE_COMPARE(vkPrimitiveTopology(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)), + VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY); +} + void EnumsTest::mapVkPrimitiveTopologyUnsupported() { CORRADE_VERIFY(!hasVkPrimitiveTopology(Magnum::MeshPrimitive::LineLoop)); @@ -157,11 +176,15 @@ void EnumsTest::mapVkPrimitiveTopologyInvalid() { std::ostringstream out; Error redirectError{&out}; - hasVkPrimitiveTopology(Magnum::MeshPrimitive(0x123)); - vkPrimitiveTopology(Magnum::MeshPrimitive(0x123)); + hasVkPrimitiveTopology(Magnum::MeshPrimitive{}); + hasVkPrimitiveTopology(Magnum::MeshPrimitive(0x12)); + vkPrimitiveTopology(Magnum::MeshPrimitive{}); + vkPrimitiveTopology(Magnum::MeshPrimitive(0x12)); CORRADE_COMPARE(out.str(), - "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x123)\n" - "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x123)\n"); + "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" + "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x12)\n" + "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" + "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x12)\n"); } void EnumsTest::mapVkIndexType() { @@ -213,11 +236,108 @@ void EnumsTest::mapVkIndexTypeInvalid() { std::ostringstream out; Error redirectError{&out}; - hasVkIndexType(Magnum::MeshIndexType(0x123)); - vkIndexType(Magnum::MeshIndexType(0x123)); + hasVkIndexType(Magnum::MeshIndexType(0x0)); + hasVkIndexType(Magnum::MeshIndexType(0x12)); + vkIndexType(Magnum::MeshIndexType(0x0)); + vkIndexType(Magnum::MeshIndexType(0x12)); + CORRADE_COMPARE(out.str(), + "Vk::hasVkIndexType(): invalid type MeshIndexType(0x0)\n" + "Vk::hasVkIndexType(): invalid type MeshIndexType(0x12)\n" + "Vk::vkIndexType(): invalid type MeshIndexType(0x0)\n" + "Vk::vkIndexType(): invalid type MeshIndexType(0x12)\n"); +} + +void EnumsTest::mapVkFormatVertexFormat() { + /* Touchstone verification */ + CORRADE_VERIFY(hasVkFormat(Magnum::VertexFormat::Vector3us)); + CORRADE_COMPARE(vkFormat(Magnum::VertexFormat::Vector3us), VK_FORMAT_R16G16B16_UINT); + + /* This goes through the first 16 bits, which should be enough. Going + through 32 bits takes 8 seconds, too much. */ + UnsignedInt firstUnhandled = 0xffff; + UnsignedInt nextHandled = 1; /* 0 is an invalid format */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto format = Magnum::VertexFormat(i); + /* Each case verifies: + - that the entries are ordered by number by comparing a function to + expected result (so insertion here is done in proper place) + - that there was no gap (unhandled value inside the range) + - that a particular vertex format maps to a particular VkFormat */ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(format) { + #define _c(format, expectedFormat) \ + case Magnum::VertexFormat::format: \ + CORRADE_COMPARE(nextHandled, i); \ + CORRADE_COMPARE(firstUnhandled, 0xffff); \ + CORRADE_VERIFY(hasVkFormat(Magnum::VertexFormat::format)); \ + CORRADE_COMPARE(vkFormat(Magnum::VertexFormat::format), VK_FORMAT_ ## expectedFormat); \ + ++nextHandled; \ + continue; + #define _s(format) \ + case Magnum::VertexFormat::format: { \ + CORRADE_COMPARE(nextHandled, i); \ + CORRADE_COMPARE(firstUnhandled, 0xffff); \ + CORRADE_VERIFY(!hasVkFormat(Magnum::VertexFormat::format)); \ + std::ostringstream out; \ + { /* Redirected otherwise graceful assert would abort */ \ + Error redirectError{&out}; \ + vkFormat(Magnum::VertexFormat::format); \ + } \ + Debug{Debug::Flag::NoNewlineAtTheEnd} << out.str(); \ + ++nextHandled; \ + continue; \ + } + #include "Magnum/Vk/Implementation/vertexFormatMapping.hpp" + #undef _s + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + + /* Not handled by any value, remember -- we might either be at the end + of the enum range (which is okay) or some value might be unhandled + here */ + firstUnhandled = i; + } + + CORRADE_COMPARE(firstUnhandled, 0xffff); +} + +void EnumsTest::mapVkFormatVertexFormatImplementationSpecific() { + CORRADE_VERIFY(hasVkFormat(Magnum::vertexFormatWrap(VK_FORMAT_A8B8G8R8_SINT_PACK32))); + CORRADE_COMPARE(vkFormat(Magnum::vertexFormatWrap(VK_FORMAT_A8B8G8R8_SINT_PACK32)), + VK_FORMAT_A8B8G8R8_SINT_PACK32); +} + +void EnumsTest::mapVkFormatVertexFormatUnsupported() { + #if 1 + CORRADE_SKIP("All vertex formats are supported."); + #else + std::ostringstream out; + Error redirectError{&out}; + + vkFormat(Magnum::VertexFormat::Vector3d); + CORRADE_COMPARE(out.str(), "Vk::vkFormat(): unsupported format VertexFormat::Vector3d\n"); + #endif +} + +void EnumsTest::mapVkFormatVertexFormatInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + hasVkFormat(Magnum::VertexFormat{}); + hasVkFormat(Magnum::VertexFormat(0x123)); + vkFormat(Magnum::VertexFormat{}); + vkFormat(Magnum::VertexFormat(0x123)); CORRADE_COMPARE(out.str(), - "Vk::hasVkIndexType(): invalid type MeshIndexType(0x123)\n" - "Vk::vkIndexType(): invalid type MeshIndexType(0x123)\n"); + "Vk::hasVkFormat(): invalid format VertexFormat(0x0)\n" + "Vk::hasVkFormat(): invalid format VertexFormat(0x123)\n" + "Vk::vkFormat(): invalid format VertexFormat(0x0)\n" + "Vk::vkFormat(): invalid format VertexFormat(0x123)\n"); } void EnumsTest::mapVkFormatPixelFormat() { diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 18c86b14a8..0e7faa2b78 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp @@ -36,13 +36,19 @@ #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/TextureData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + namespace Magnum { namespace Trade { AnySceneImporter::AnySceneImporter(PluginManager::Manager& manager): AbstractImporter{manager} {} @@ -175,6 +181,16 @@ Int AnySceneImporter::doObject3DForName(const std::string& name) { return _in->o std::string AnySceneImporter::doObject3DName(const UnsignedInt id) { return _in->object3DName(id); } Containers::Pointer AnySceneImporter::doObject3D(const UnsignedInt id) { return _in->object3D(id); } +UnsignedInt AnySceneImporter::doMeshCount() const { return _in->meshCount(); } +Int AnySceneImporter::doMeshForName(const std::string& name) { return _in->meshForName(name); } +std::string AnySceneImporter::doMeshName(const UnsignedInt id) { return _in->meshName(id); } +Containers::Optional AnySceneImporter::doMesh(const UnsignedInt id, const UnsignedInt level) { return _in->mesh(id, level); } + +MeshAttribute AnySceneImporter::doMeshAttributeForName(const std::string& name) { return _in->meshAttributeForName(name); } +std::string AnySceneImporter::doMeshAttributeName(const UnsignedShort id) { return _in->meshAttributeName(meshAttributeCustom(id)); } + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH UnsignedInt AnySceneImporter::doMesh2DCount() const { return _in->mesh2DCount(); } Int AnySceneImporter::doMesh2DForName(const std::string& name) { return _in->mesh2DForName(name); } std::string AnySceneImporter::doMesh2DName(const UnsignedInt id) { return _in->mesh2DName(id); } @@ -184,6 +200,8 @@ UnsignedInt AnySceneImporter::doMesh3DCount() const { return _in->mesh3DCount(); Int AnySceneImporter::doMesh3DForName(const std::string& name) { return _in->mesh3DForName(name); } std::string AnySceneImporter::doMesh3DName(const UnsignedInt id) { return _in->mesh3DName(id); } Containers::Optional AnySceneImporter::doMesh3D(const UnsignedInt id) { return _in->mesh3D(id); } +CORRADE_IGNORE_DEPRECATED_POP +#endif UnsignedInt AnySceneImporter::doMaterialCount() const { return _in->materialCount(); } Int AnySceneImporter::doMaterialForName(const std::string& name) { return _in->materialForName(name); } diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h index 1cb4868b0b..743bd86a40 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h @@ -175,15 +175,29 @@ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doObject3DName(UnsignedInt id) override; MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Pointer doObject3D(UnsignedInt id) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMeshCount() const override; + MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMeshForName(const std::string& name) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMeshName(UnsignedInt id) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override; + + MAGNUM_ANYSCENEIMPORTER_LOCAL MeshAttribute doMeshAttributeForName(const std::string& name) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMeshAttributeName(UnsignedShort id) override; + + #ifdef MAGNUM_BUILD_DEPRECATED MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMesh2DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMesh2DForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMesh2DName(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_PUSH MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMesh2D(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_POP MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMesh3DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMesh3DForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMesh3DName(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_PUSH MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMesh3D(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMaterialCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMaterialForName(const std::string& name) override; diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 325f4cf2bc..722754bb96 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -33,7 +33,13 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "Magnum/Trade/MeshData3D.h" +#endif #include "configure.h" @@ -43,6 +49,9 @@ struct AnySceneImporterTest: TestSuite::Tester { explicit AnySceneImporterTest(); void load(); + #ifdef MAGNUM_BUILD_DEPRECATED + void loadDeprecatedMeshData(); + #endif void detect(); void unknown(); @@ -79,6 +88,10 @@ AnySceneImporterTest::AnySceneImporterTest() { addInstancedTests({&AnySceneImporterTest::load}, Containers::arraySize(LoadData)); + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AnySceneImporterTest::loadDeprecatedMeshData}); + #endif + addInstancedTests({&AnySceneImporterTest::detect}, Containers::arraySize(DetectData)); @@ -106,13 +119,35 @@ void AnySceneImporterTest::load() { CORRADE_VERIFY(importer->openFile(data.filename)); /* Check only size, as it is good enough proof that it is working */ + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->vertexCount(), 3); + + importer->close(); + CORRADE_VERIFY(!importer->isOpened()); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AnySceneImporterTest::loadDeprecatedMeshData() { + if(!(_manager.loadState("ObjImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("ObjImporter plugin not enabled, cannot test"); + + Containers::Pointer importer = _manager.instantiate("AnySceneImporter"); + CORRADE_VERIFY(importer->openFile(OBJ_FILE)); + + /* Check only size, as it is good enough proof that it is working */ + + /* MSVC warns also on positions() */ + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional mesh = importer->mesh3D(0); CORRADE_VERIFY(mesh); CORRADE_COMPARE(mesh->positions(0).size(), 3); + CORRADE_IGNORE_DEPRECATED_POP importer->close(); CORRADE_VERIFY(!importer->isOpened()); } +#endif void AnySceneImporterTest::detect() { auto&& data = DetectData[testCaseInstanceId()]; diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index e81778c785..af0b8f39a4 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -29,16 +29,18 @@ #include #include #include -#include +#include #include +#include #include #include #include "Magnum/Mesh.h" -#include "Magnum/MeshTools/CombineIndexedArrays.h" +#include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Trade { @@ -58,7 +60,7 @@ void ignoreLine(std::istream& in) { template Math::Vector extractFloatData(const std::string& str, Float* extra = nullptr) { std::vector data = Utility::String::splitWithoutEmptyParts(str, ' '); if(data.size() < size || data.size() > size + (extra ? 1 : 0)) { - Error() << "Trade::ObjImporter::mesh3D(): invalid float array size"; + Error() << "Trade::ObjImporter::mesh(): invalid float array size"; throw 0; } @@ -78,16 +80,6 @@ template Math::Vector extractFloatData(const std: return output; } -template void reindex(const std::vector& indices, std::vector& data) { - /* Check that indices are in range */ - for(UnsignedInt i: indices) if(i >= data.size()) { - Error() << "Trade::ObjImporter::mesh3D(): index out of range"; - throw 0; - } - - data = MeshTools::duplicate(indices, data); -} - } ObjImporter::ObjImporter() = default; @@ -214,18 +206,34 @@ void ObjImporter::parseMeshNames() { std::get<1>(_file->meshes.back()) = _file->in->tellg(); } -UnsignedInt ObjImporter::doMesh3DCount() const { return _file->meshes.size(); } +UnsignedInt ObjImporter::doMeshCount() const { return _file->meshes.size(); } -Int ObjImporter::doMesh3DForName(const std::string& name) { +Int ObjImporter::doMeshForName(const std::string& name) { const auto it = _file->meshesForName.find(name); return it == _file->meshesForName.end() ? -1 : it->second; } -std::string ObjImporter::doMesh3DName(UnsignedInt id) { +std::string ObjImporter::doMeshName(UnsignedInt id) { return _file->meshNames[id]; } -Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { +namespace { + +template bool checkAndDuplicateInto(const Containers::StridedArrayView1D& indices, const Containers::Array& data, const Containers::StridedArrayView1D& out, UnsignedInt offset) { + /* Check that indices are in range. Add back the original index offset for + easier data debugging. */ + for(UnsignedInt i: indices) if(i >= data.size()) { + Error{} << "Trade::ObjImporter::mesh(): index" << (i + offset) << "out of range for" << data.size() << "vertices"; + return false; + } + + MeshTools::duplicateInto(indices, stridedArrayView(data), out); + return true; +} + +} + +Containers::Optional ObjImporter::doMesh(UnsignedInt id, UnsignedInt) { /* Seek the file, set mesh parsing parameters */ std::streampos begin, end; UnsignedInt positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset; @@ -233,12 +241,13 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { _file->in->seekg(begin); Containers::Optional primitive; - std::vector positions; - std::vector> textureCoordinates; - std::vector> normals; - std::vector positionIndices; - std::vector textureCoordinateIndices; - std::vector normalIndices; + Containers::Array positions; + Containers::Array normals; + Containers::Array textureCoordinates; + /* Taking a shortcut as there's fortunately nothing else than just 3 types + of data. First positions, then normals, then texture coordinates. */ + Containers::Array indices; + std::size_t textureCoordinateIndexCount = 0, normalIndexCount = 0; try { while(_file->in->good() && _file->in->tellg() < end) { /* Ignore comments */ @@ -266,28 +275,26 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { Float extra{1.0f}; const Vector3 data = extractFloatData<3>(contents, &extra); if(!Math::TypeTraits::equals(extra, 1.0f)) { - Error() << "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported"; + Error() << "Trade::ObjImporter::mesh(): homogeneous coordinates are not supported"; return Containers::NullOpt; } - positions.push_back(data); + arrayAppend(positions, data); /* Texture coordinate */ } else if(keyword == "vt") { Float extra{0.0f}; - const auto data = extractFloatData<2>(contents, &extra); + const Vector2 data = extractFloatData<2>(contents, &extra); if(!Math::TypeTraits::equals(extra, 0.0f)) { - Error() << "Trade::ObjImporter::mesh3D(): 3D texture coordinates are not supported"; + Error() << "Trade::ObjImporter::mesh(): 3D texture coordinates are not supported"; return Containers::NullOpt; } - if(textureCoordinates.empty()) textureCoordinates.emplace_back(); - textureCoordinates.front().emplace_back(data); + arrayAppend(textureCoordinates, data); /* Normal */ } else if(keyword == "vn") { - if(normals.empty()) normals.emplace_back(); - normals.front().emplace_back(extractFloatData<3>(contents)); + arrayAppend(normals, Vector3{extractFloatData<3>(contents)}); /* Indices */ } else if(keyword == "p" || keyword == "l" || keyword == "f") { @@ -297,13 +304,13 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { if(keyword == "p") { /* Check that we don't mix the primitives in one mesh */ if(primitive && primitive != MeshPrimitive::Points) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Points; + Error() << "Trade::ObjImporter::mesh(): mixed primitive" << *primitive << "and" << MeshPrimitive::Points; return Containers::NullOpt; } /* Check vertex count per primitive */ if(indexTuples.size() != 1) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for point"; + Error() << "Trade::ObjImporter::mesh(): wrong index count for point"; return Containers::NullOpt; } @@ -313,13 +320,13 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } else if(keyword == "l") { /* Check that we don't mix the primitives in one mesh */ if(primitive && primitive != MeshPrimitive::Lines) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Lines; + Error() << "Trade::ObjImporter::mesh(): mixed primitive" << *primitive << "and" << MeshPrimitive::Lines; return Containers::NullOpt; } /* Check vertex count per primitive */ if(indexTuples.size() != 2) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for line"; + Error() << "Trade::ObjImporter::mesh(): wrong index count for line"; return Containers::NullOpt; } @@ -329,16 +336,16 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } else if(keyword == "f") { /* Check that we don't mix the primitives in one mesh */ if(primitive && primitive != MeshPrimitive::Triangles) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Triangles; + Error() << "Trade::ObjImporter::mesh(): mixed primitive" << *primitive << "and" << MeshPrimitive::Triangles; return Containers::NullOpt; } /* Check vertex count per primitive */ if(indexTuples.size() < 3) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for triangle"; + Error() << "Trade::ObjImporter::mesh(): wrong index count for triangle"; return Containers::NullOpt; } else if(indexTuples.size() != 3) { - Error() << "Trade::ObjImporter::mesh3D(): polygons are not supported"; + Error() << "Trade::ObjImporter::mesh(): polygons are not supported"; return Containers::NullOpt; } @@ -347,22 +354,30 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ for(const std::string& indexTuple: indexTuples) { - std::vector indices = Utility::String::split(indexTuple, '/'); - if(indices.size() > 3) { - Error() << "Trade::ObjImporter::mesh3D(): invalid index data"; + std::vector indexStrings = Utility::String::split(indexTuple, '/'); + if(indexStrings.size() > 3) { + Error() << "Trade::ObjImporter::mesh(): invalid index data"; return Containers::NullOpt; } + Vector3ui index; + /* Position indices */ - positionIndices.push_back(std::stoul(indices[0]) - positionIndexOffset); + index[0] = std::stoul(indexStrings[0]) - positionIndexOffset; /* Texture coordinates */ - if(indices.size() == 2 || (indices.size() == 3 && !indices[1].empty())) - textureCoordinateIndices.push_back(std::stoul(indices[1]) - textureCoordinateIndexOffset); + if(indexStrings.size() == 2 || (indexStrings.size() == 3 && !indexStrings[1].empty())) { + index[2] = std::stoul(indexStrings[1]) - textureCoordinateIndexOffset; + ++textureCoordinateIndexCount; + } /* Normal indices */ - if(indices.size() == 3) - normalIndices.push_back(std::stoul(indices[2]) - normalIndexOffset); + if(indexStrings.size() == 3) { + index[1] = std::stoul(indexStrings[2]) - normalIndexOffset; + ++normalIndexCount; + } + + arrayAppend(indices, index); } /* Ignore unsupported keywords, error out on unknown keywords */ @@ -372,12 +387,12 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { if(keyword == expected) return true; return false; }()) { - Error() << "Trade::ObjImporter::mesh3D(): unknown keyword" << keyword; + Error() << "Trade::ObjImporter::mesh(): unknown keyword" << keyword; return Containers::NullOpt; } }} catch(const std::exception&) { - Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; + Error() << "Trade::ObjImporter::mesh(): error while converting numeric data"; return Containers::NullOpt; } catch(...) { /* Error message already printed */ @@ -385,64 +400,87 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } /* There should be at least indexed position data */ - if(positions.empty() || positionIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete position data"; + if(positions.empty() || indices.empty()) { + Error() << "Trade::ObjImporter::mesh(): incomplete position data"; return Containers::NullOpt; } /* If there are index data, there should be also vertex data (and also the other way) */ - if(normals.empty() != normalIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete normal data"; + if(normals.empty() != (normalIndexCount == 0)) { + Error() << "Trade::ObjImporter::mesh(): incomplete normal data"; return Containers::NullOpt; } - if(textureCoordinates.empty() != textureCoordinateIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data"; + if(textureCoordinates.empty() != (textureCoordinateIndexCount == 0)) { + Error() << "Trade::ObjImporter::mesh(): incomplete texture coordinate data"; return Containers::NullOpt; } /* All index arrays should have the same length */ - if(!normalIndices.empty() && normalIndices.size() != positionIndices.size()) { - CORRADE_INTERNAL_ASSERT(normalIndices.size() < positionIndices.size()); - Error() << "Trade::ObjImporter::mesh3D(): some normal indices are missing"; + if(normalIndexCount && normalIndexCount != indices.size()) { + CORRADE_INTERNAL_ASSERT(normalIndexCount < indices.size()); + Error() << "Trade::ObjImporter::mesh(): some normal indices are missing"; return Containers::NullOpt; } - if(!textureCoordinates.empty() && textureCoordinateIndices.size() != positionIndices.size()) { - CORRADE_INTERNAL_ASSERT(textureCoordinateIndices.size() < positionIndices.size()); - Error() << "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing"; + if(textureCoordinateIndexCount && textureCoordinateIndexCount != indices.size()) { + CORRADE_INTERNAL_ASSERT(textureCoordinateIndexCount < indices.size()); + Error() << "Trade::ObjImporter::mesh(): some texture coordinate indices are missing"; return Containers::NullOpt; } - /* Merge index arrays, if there aren't just the positions */ - std::vector indices; - if(!normalIndices.empty() || !textureCoordinateIndices.empty()) { - std::vector>> arrays; - arrays.reserve(3); - arrays.emplace_back(positionIndices); - if(!normalIndices.empty()) arrays.emplace_back(normalIndices); - if(!textureCoordinateIndices.empty()) arrays.emplace_back(textureCoordinateIndices); - indices = MeshTools::combineIndexArrays(arrays); - - /* Reindex data arrays */ - try { - reindex(positionIndices, positions); - if(!normalIndices.empty()) reindex(normalIndices, normals.front()); - if(!textureCoordinateIndices.empty()) reindex(textureCoordinateIndices, textureCoordinates.front()); - } catch(...) { - /* Error message already printed */ + /* Merge index arrays. If any of the attributes was not there, the whole + index array has zeros, not affecting the uniqueness in any way. */ + Containers::Array indexData{Containers::NoInit, indices.size()*sizeof(UnsignedInt)}; + const auto indexDataI = Containers::arrayCast(indexData); + const std::size_t vertexCount = MeshTools::removeDuplicatesInPlaceInto( + Containers::arrayCast<2, char>(arrayView(indices)), indexDataI); + + /* Allocate attribute and vertex data */ + std::size_t attributeCount = 1; + UnsignedInt stride = sizeof(Vector3); + if(normalIndexCount) { + ++attributeCount; + stride += sizeof(Vector3); + } + if(textureCoordinateIndexCount) { + ++attributeCount; + stride += sizeof(Vector2); + } + Containers::Array attributeData{attributeCount}; + Containers::Array vertexData{Containers::NoInit, vertexCount*stride}; + + /* Duplicate the vertices into the output */ + const auto indicesPerAttribute = Containers::arrayCast<2, const UnsignedInt>(stridedArrayView(indices)).transposed<0, 1>(); + std::size_t attributeIndex = 0; + std::size_t offset = 0; + { + Containers::StridedArrayView1D view{vertexData, + reinterpret_cast(vertexData.data()), vertexCount, stride}; + if(!checkAndDuplicateInto(indicesPerAttribute[0].prefix(vertexCount), positions, view, positionIndexOffset)) return Containers::NullOpt; - } - - /* Otherwise just use the original position index array. Don't forget to - check range */ - } else { - indices = std::move(positionIndices); - for(UnsignedInt i: indices) if(i >= positions.size()) { - Error() << "Trade::ObjImporter::mesh3D(): index out of range"; + attributeData[attributeIndex++] = MeshAttributeData{MeshAttribute::Position, view}; + offset += sizeof(Vector3); + } + if(normalIndexCount) { + Containers::StridedArrayView1D view{vertexData, + reinterpret_cast(vertexData.data() + offset), vertexCount, stride}; + if(!checkAndDuplicateInto(indicesPerAttribute[1].prefix(vertexCount), normals, view, normalIndexOffset)) return Containers::NullOpt; - } + attributeData[attributeIndex++] = MeshAttributeData{MeshAttribute::Normal, view}; + offset += sizeof(Vector3); + } + if(textureCoordinateIndexCount) { + Containers::StridedArrayView1D view{vertexData, + reinterpret_cast(vertexData.data() + offset), vertexCount, stride}; + if(!checkAndDuplicateInto(indicesPerAttribute[2].prefix(vertexCount), textureCoordinates, view, textureCoordinateIndexOffset)) + return Containers::NullOpt; + attributeData[attributeIndex++] = MeshAttributeData{MeshAttribute::TextureCoordinates, view}; + offset += sizeof(Vector2); } + CORRADE_INTERNAL_ASSERT(offset == stride && attributeIndex == attributeCount); - return MeshData3D{*primitive, std::move(indices), {std::move(positions)}, std::move(normals), std::move(textureCoordinates), {}, nullptr}; + return MeshData{*primitive, + std::move(indexData), Trade::MeshIndexData{indexDataI}, + std::move(vertexData), std::move(attributeData)}; } }} diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.h b/src/MagnumPlugins/ObjImporter/ObjImporter.h index 18987589da..0f49c2590d 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.h +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.h @@ -91,8 +91,12 @@ See @ref building, @ref cmake and @ref plugins for more information. @section Trade-ObjImporter-limitations Behavior and limitations -Polygons (quads etc.), automatic normal generation and material properties are -currently not supported. +Meshes are imported as @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions with optional @ref VertexFormat::Vector3 normals and +@ref VertexFormat::Vector2 texture coordinates, if present in the source file. + +Polygons (quads etc.) and material properties are currently not supported. */ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { public: @@ -114,10 +118,10 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL void doOpenFile(const std::string& filename) override; MAGNUM_OBJIMPORTER_LOCAL void doClose() override; - MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMesh3DCount() const override; - MAGNUM_OBJIMPORTER_LOCAL Int doMesh3DForName(const std::string& name) override; - MAGNUM_OBJIMPORTER_LOCAL std::string doMesh3DName(UnsignedInt id) override; - MAGNUM_OBJIMPORTER_LOCAL Containers::Optional doMesh3D(UnsignedInt id) override; + MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMeshCount() const override; + MAGNUM_OBJIMPORTER_LOCAL Int doMeshForName(const std::string& name) override; + MAGNUM_OBJIMPORTER_LOCAL std::string doMeshName(UnsignedInt id) override; + MAGNUM_OBJIMPORTER_LOCAL Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override; MAGNUM_OBJIMPORTER_LOCAL void parseMeshNames(); diff --git a/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp b/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp index 311da3a9ab..c8a02b302a 100644 --- a/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp +++ b/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp @@ -26,13 +26,14 @@ #include #include #include +#include #include #include #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -154,591 +155,646 @@ ObjImporterTest::ObjImporterTest() { void ObjImporterTest::pointMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "pointMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 2, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 1); + /* The points get reordered according to the index buffer. Might not be a + problem in general but it is when relying on the order */ + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {2.0f, 3.0f, 5.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::lineMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "lineMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 1, 2 - })); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 1, 2}), + TestSuite::Compare::Container); } void ObjImporterTest::triangleMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "triangleMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f}, - {2.5f, 0.0f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.0f}, + {2.5f, 0.0f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::mixedPrimitives() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "mixedPrimitives.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(0)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): mixed primitive MeshPrimitive::Points and MeshPrimitive::Lines\n"); + CORRADE_VERIFY(!importer->mesh(0)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): mixed primitive MeshPrimitive::Points and MeshPrimitive::Lines\n"); } void ObjImporterTest::positionsOnly() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "triangleMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_VERIFY(!data->hasNormals()); - CORRADE_VERIFY(!data->hasTextureCoords2D()); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_VERIFY(data->hasAttribute(MeshAttribute::Position)); } void ObjImporterTest::textureCoordinates() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "textureCoordinates.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_VERIFY(!data->hasNormals()); - CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->textureCoords2D(0), (std::vector{ - {1.0f, 0.5f}, - {1.0f, 0.5f}, - {0.5f, 1.0f}, - {0.5f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 2); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {1.0f, 0.5f}, + {1.0f, 0.5f}, + {0.5f, 1.0f}, + {0.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::normals() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "normals.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_VERIFY(!data->hasTextureCoords2D()); - CORRADE_COMPARE(data->normalArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->normals(0), (std::vector{ - {1.0f, 0.5f, 3.5f}, - {1.0f, 0.5f, 3.5f}, - {0.5f, 1.0f, 0.5f}, - {0.5f, 1.0f, 0.5f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 2); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {1.0f, 0.5f, 3.5f}, + {1.0f, 0.5f, 3.5f}, + {0.5f, 1.0f, 0.5f}, + {0.5f, 1.0f, 0.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::textureCoordinatesNormals() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "textureCoordinatesNormals.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(data->normalArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->textureCoords2D(0), (std::vector{ - {1.0f, 0.5f}, - {1.0f, 0.5f}, - {0.5f, 1.0f}, - {0.5f, 1.0f}, - {0.5f, 1.0f} - })); - CORRADE_COMPARE(data->normals(0), (std::vector{ - {1.0f, 0.5f, 3.5f}, - {0.5f, 1.0f, 0.5f}, - {0.5f, 1.0f, 0.5f}, - {1.0f, 0.5f, 3.5f}, - {0.5f, 1.0f, 0.5f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0, 4, 2 - })); + CORRADE_COMPARE(data->attributeCount(), 3); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {1.0f, 0.5f}, + {1.0f, 0.5f}, + {0.5f, 1.0f}, + {0.5f, 1.0f}, + {0.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {1.0f, 0.5f, 3.5f}, + {0.5f, 1.0f, 0.5f}, + {0.5f, 1.0f, 0.5f}, + {1.0f, 0.5f, 3.5f}, + {0.5f, 1.0f, 0.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0, 4, 2}), + TestSuite::Compare::Container); } void ObjImporterTest::emptyFile() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "emptyFile.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); } void ObjImporterTest::unnamedMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "emptyFile.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); - CORRADE_COMPARE(importer->mesh3DName(0), ""); - CORRADE_COMPARE(importer->mesh3DForName(""), -1); + CORRADE_COMPARE(importer->meshCount(), 1); + CORRADE_COMPARE(importer->meshName(0), ""); + CORRADE_COMPARE(importer->meshForName(""), -1); } void ObjImporterTest::namedMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "namedMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); - CORRADE_COMPARE(importer->mesh3DName(0), "MyMesh"); - CORRADE_COMPARE(importer->mesh3DForName("MyMesh"), 0); + CORRADE_COMPARE(importer->meshCount(), 1); + CORRADE_COMPARE(importer->meshName(0), "MyMesh"); + CORRADE_COMPARE(importer->meshForName("MyMesh"), 0); } void ObjImporterTest::moreMeshes() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "moreMeshes.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 3); + CORRADE_COMPARE(importer->meshCount(), 3); - CORRADE_COMPARE(importer->mesh3DName(0), "PointMesh"); - CORRADE_COMPARE(importer->mesh3DForName("PointMesh"), 0); - const Containers::Optional data = importer->mesh3D(0); + CORRADE_COMPARE(importer->meshName(0), "PointMesh"); + CORRADE_COMPARE(importer->meshForName("PointMesh"), 0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1 - })); - - CORRADE_COMPARE(importer->mesh3DName(1), "LineMesh"); - CORRADE_COMPARE(importer->mesh3DForName("LineMesh"), 1); - const Containers::Optional data1 = importer->mesh3D(1); + CORRADE_COMPARE(data->attributeCount(), 2); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(importer->meshName(1), "LineMesh"); + CORRADE_COMPARE(importer->meshForName("LineMesh"), 1); + const Containers::Optional data1 = importer->mesh(1); CORRADE_VERIFY(data1); CORRADE_COMPARE(data1->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data1->positionArrayCount(), 1); - CORRADE_COMPARE(data1->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data1->indices(), (std::vector{ - 0, 1, 1, 0 - })); - - CORRADE_COMPARE(importer->mesh3DName(2), "TriangleMesh"); - CORRADE_COMPARE(importer->mesh3DForName("TriangleMesh"), 2); - const Containers::Optional data2 = importer->mesh3D(2); + CORRADE_COMPARE(data1->attributeCount(), 2); + CORRADE_COMPARE_AS(data1->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data1->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.5f, 2.0f}, + {0.0f, 1.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data1->isIndexed()); + CORRADE_COMPARE(data1->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data1->indices(), + Containers::arrayView({0, 1, 1, 0}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(importer->meshName(2), "TriangleMesh"); + CORRADE_COMPARE(importer->meshForName("TriangleMesh"), 2); + const Containers::Optional data2 = importer->mesh(2); CORRADE_VERIFY(data2); CORRADE_COMPARE(data2->primitive(), MeshPrimitive::Triangles); - CORRADE_COMPARE(data2->positionArrayCount(), 1); - CORRADE_COMPARE(data2->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.5f} - })); - CORRADE_COMPARE(data2->indices(), (std::vector{ - 0, 1, 2, 2, 1, 0 - })); + CORRADE_COMPARE(data2->attributeCount(), 3); + CORRADE_COMPARE_AS(data2->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.5f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data2->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.5f, 2.0f, 3.0f}, + {0.5f, 2.0f, 3.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data2->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.5f, 2.0f}, + {0.5f, 2.0f}, + {0.5f, 2.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data2->isIndexed()); + CORRADE_COMPARE(data2->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data2->indices(), + Containers::arrayView({0, 1, 2, 2, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::unnamedFirstMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "unnamedFirstMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 2); + CORRADE_COMPARE(importer->meshCount(), 2); - CORRADE_COMPARE(importer->mesh3DName(0), ""); - CORRADE_COMPARE(importer->mesh3DForName(""), -1); + CORRADE_COMPARE(importer->meshName(0), ""); + CORRADE_COMPARE(importer->meshForName(""), -1); - CORRADE_COMPARE(importer->mesh3DName(1), "SecondMesh"); - CORRADE_COMPARE(importer->mesh3DForName("SecondMesh"), 1); + CORRADE_COMPARE(importer->meshName(1), "SecondMesh"); + CORRADE_COMPARE(importer->meshForName("SecondMesh"), 1); } void ObjImporterTest::wrongFloat() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("WrongFloat"); + const Int id = importer->meshForName("WrongFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): error while converting numeric data\n"); } void ObjImporterTest::wrongInteger() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("WrongInteger"); + const Int id = importer->meshForName("WrongInteger"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): error while converting numeric data\n"); } void ObjImporterTest::unmergedIndexOutOfRange() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("PositionIndexOutOfRange"); + const Int id = importer->meshForName("PositionIndexOutOfRange"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): index out of range\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 1 out of range for 1 vertices\n"); } void ObjImporterTest::mergedIndexOutOfRange() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("TextureIndexOutOfRange"); + const Int id = importer->meshForName("TextureIndexOutOfRange"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): index out of range\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 2 out of range for 1 vertices\n"); } void ObjImporterTest::zeroIndex() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("ZeroIndex"); + const Int id = importer->meshForName("ZeroIndex"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): index out of range\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 0 out of range for 1 vertices\n"); } void ObjImporterTest::explicitOptionalPositionCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("SupportedPositionW"); + const Int id = importer->meshForName("SupportedPositionW"); CORRADE_VERIFY(id > -1); - const Containers::Optional data = importer->mesh3D(id); + const Containers::Optional data = importer->mesh(id); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {1.5f, 2.0f, 3.0f} - })); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {1.5f, 2.0f, 3.0f} + }), TestSuite::Compare::Container); } void ObjImporterTest::explicitOptionalTextureCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("SupportedTextureW"); + const Int id = importer->meshForName("SupportedTextureW"); CORRADE_VERIFY(id > -1); - const Containers::Optional data = importer->mesh3D(id); + const Containers::Optional data = importer->mesh(id); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(data->textureCoords2D(0), (std::vector{ - {0.5f, 0.7f} - })); + CORRADE_COMPARE(data->attributeCount(MeshAttribute::TextureCoordinates), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.5f, 0.7f} + }), TestSuite::Compare::Container); } void ObjImporterTest::unsupportedOptionalPositionCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("UnsupportedPositionW"); + const Int id = importer->meshForName("UnsupportedPositionW"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): homogeneous coordinates are not supported\n"); } void ObjImporterTest::unsupportedOptionalTextureCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("UnsupportedTextureW"); + const Int id = importer->meshForName("UnsupportedTextureW"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): 3D texture coordinates are not supported\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): 3D texture coordinates are not supported\n"); } void ObjImporterTest::shortFloatData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("ShortFloat"); + const Int id = importer->meshForName("ShortFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid float array size\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longFloatData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("LongFloat"); + const Int id = importer->meshForName("LongFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid float array size\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longOptionalFloatData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("LongOptionalFloat"); + const Int id = importer->meshForName("LongOptionalFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid float array size\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("InvalidIndices"); + const Int id = importer->meshForName("InvalidIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid index data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid index data\n"); } void ObjImporterTest::wrongPointIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("WrongPointIndices"); + const Int id = importer->meshForName("WrongPointIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): wrong index count for point\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for point\n"); } void ObjImporterTest::wrongLineIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("WrongLineIndices"); + const Int id = importer->meshForName("WrongLineIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): wrong index count for line\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for line\n"); } void ObjImporterTest::wrongTriangleIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("WrongTriangleIndices"); + const Int id = importer->meshForName("WrongTriangleIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): wrong index count for triangle\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for triangle\n"); } void ObjImporterTest::polygonIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("PolygonIndices"); + const Int id = importer->meshForName("PolygonIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): polygons are not supported\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): polygons are not supported\n"); } void ObjImporterTest::missingPositionData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingPositionData"); + const Int id = importer->meshForName("MissingPositionData"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete position data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete position data\n"); } void ObjImporterTest::missingPositionIndices() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingPositionIndices"); + const Int id = importer->meshForName("MissingPositionIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete position data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete position data\n"); } void ObjImporterTest::missingNormalData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingNormalData"); + const Int id = importer->meshForName("MissingNormalData"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete normal data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete normal data\n"); } void ObjImporterTest::missingNormalIndices() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingNormalIndices"); + const Int id = importer->meshForName("MissingNormalIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete normal data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete normal data\n"); } void ObjImporterTest::missingTextureCoordinateData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingTextureData"); + const Int id = importer->meshForName("MissingTextureData"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete texture coordinate data\n"); } void ObjImporterTest::missingTextureCoordinateIndices() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingTextureIndices"); + const Int id = importer->meshForName("MissingTextureIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete texture coordinate data\n"); } void ObjImporterTest::wrongNormalIndexCount() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongIndexCount.obj"))); - const Int id = importer->mesh3DForName("ShortNormalIndices"); + const Int id = importer->meshForName("ShortNormalIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): some normal indices are missing\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): some normal indices are missing\n"); } void ObjImporterTest::wrongTextureCoordinateIndexCount() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongIndexCount.obj"))); - const Int id = importer->mesh3DForName("ShortTextureIndices"); + const Int id = importer->meshForName("ShortTextureIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): some texture coordinate indices are missing\n"); } void ObjImporterTest::unsupportedKeyword() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj"))); - const Int id = importer->mesh3DForName("UnsupportedKeyword"); + const Int id = importer->meshForName("UnsupportedKeyword"); CORRADE_VERIFY(id > -1); /* Everything should be parsed properly */ - const Containers::Optional data = importer->mesh3D(id); + const Containers::Optional data = importer->mesh(id); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.0f, 1.0f, 2.0f} - })); - CORRADE_COMPARE(data->indices(), std::vector{0}); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.0f, 1.0f, 2.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0}), + TestSuite::Compare::Container); } void ObjImporterTest::unknownKeyword() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj"))); - const Int id = importer->mesh3DForName("UnknownKeyword"); + const Int id = importer->meshForName("UnknownKeyword"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): unknown keyword bleh\n"); + CORRADE_VERIFY(!importer->mesh(id)); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): unknown keyword bleh\n"); } }}}}