diff --git a/doc/changelog.dox b/doc/changelog.dox index c1150095e..1f291d811 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -109,6 +109,8 @@ See also: @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() utility for checking if @ref Trade::MeshData is interleaved - Added @ref MeshTools::interleavedLayout() for convenient creation of an diff --git a/doc/snippets/MagnumMeshTools-gl.cpp b/doc/snippets/MagnumMeshTools-gl.cpp index 39ad83a02..bd93ffada 100644 --- a/doc/snippets/MagnumMeshTools-gl.cpp +++ b/doc/snippets/MagnumMeshTools-gl.cpp @@ -29,13 +29,46 @@ #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; diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index be538c5e9..efbf610b1 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -60,9 +60,11 @@ 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) diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 1cb807b96..703c2f9c8 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -25,8 +25,9 @@ #include "Compile.h" +#include #include -#include /** @todo remove once MeshData is sane */ +#include /** @todo remove once MeshDataXD is gone */ #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" @@ -36,6 +37,7 @@ #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" @@ -45,6 +47,150 @@ namespace Magnum { namespace MeshTools { +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{}); + /* Right now this could fire only if we have 2D positions, which is + unlikely; in the future it might fire once packed formats are added */ + 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. Again this won't + fire now, but might in the future once packed formats are added */ + } 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); + return compile(meshData); +} + +GL::Mesh compile(const Trade::MeshData& 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()); + + return compile(meshData, std::move(indices), std::move(vertices)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices) { + return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices) { + return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices) { + return compile(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices) { + 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; + switch(meshData.attributeName(i)) { + case Trade::MeshAttribute::Position: + if(meshData.attributeFormat(i) == VertexFormat::Vector2) + attribute.emplace(Shaders::Generic2D::Position{}); + else if(meshData.attributeFormat(i) == VertexFormat::Vector3) + attribute.emplace(Shaders::Generic3D::Position{}); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + break; + case Trade::MeshAttribute::Normal: + CORRADE_INTERNAL_ASSERT(meshData.attributeFormat(i) == VertexFormat::Vector3); + attribute.emplace(Shaders::Generic3D::Normal{}); + break; + case Trade::MeshAttribute::TextureCoordinates: + CORRADE_INTERNAL_ASSERT(meshData.attributeFormat(i) == VertexFormat::Vector2); + /** @todo have Generic2D derived from Generic that has all + attribute definitions common for 2D and 3D */ + attribute.emplace(Shaders::Generic2D::TextureCoordinates{}); + break; + case Trade::MeshAttribute::Color: + /** @todo have Generic2D derived from Generic that has all + attribute definitions common for 2D and 3D */ + if(meshData.attributeFormat(i) == VertexFormat::Vector3) + attribute.emplace(Shaders::Generic2D::Color3{}); + else if(meshData.attributeFormat(i) == VertexFormat::Vector4) + attribute.emplace(Shaders::Generic2D::Color4{}); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + break; + + /* So it doesn't yell that we didn't handle a known attribute */ + case Trade::MeshAttribute::Custom: break; /* LCOV_EXCL_LINE */ + } + + if(!attribute) { + 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 compile(const Trade::MeshData2D& meshData) { GL::Mesh mesh; mesh.setPrimitive(meshData.primitive()); diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index cd975994f..50d5e9c34 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -85,6 +85,97 @@ 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. + +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. +*/ +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); + /** @brief Compile 2D mesh data diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 899dc8826..31939f6eb 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -100,7 +100,7 @@ if(BUILD_GL_TESTS) MagnumDebugTools MagnumGL MagnumOpenGLTester - MagnumMeshTools + MagnumMeshToolsTestLib MagnumShaders FILES CompileTestFiles/color2D.tga diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index d54cebb60..0ffc8a0e9 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" @@ -78,8 +81,14 @@ struct CompileGLTest: GL::OpenGLTester { public: explicit CompileGLTest(); - void twoDimensions(); - void threeDimensions(); + template void twoDimensions(); + template void threeDimensions(); + void unknownAttribute(); + void generateNormalsNoPosition(); + void generateNormals2DPosition(); + + void externalBuffers(); + void externalBuffersInvalid(); private: PluginManager::Manager _manager{"nonexistent"}; @@ -138,6 +147,17 @@ 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} +}; + using namespace Math::Literals; constexpr Color4ub ImageData[] { @@ -148,11 +168,24 @@ constexpr Color4ub ImageData[] { }; CompileGLTest::CompileGLTest() { - addInstancedTests({&CompileGLTest::twoDimensions}, - Containers::arraySize(Data2D)); + addInstancedTests({ + &CompileGLTest::twoDimensions, + &CompileGLTest::twoDimensions}, + Containers::arraySize(Data2D)); + + addInstancedTests({ + &CompileGLTest::threeDimensions, + &CompileGLTest::threeDimensions}, + Containers::arraySize(Data3D)); - addInstancedTests({&CompileGLTest::threeDimensions}, - Containers::arraySize(Data3D)); + addTests({&CompileGLTest::unknownAttribute, + &CompileGLTest::generateNormalsNoPosition, + &CompileGLTest::generateNormals2DPosition}); + + 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 +220,19 @@ 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"; } +}; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData2D"; } +}; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData3D"; } +}; + +template void CompileGLTest::twoDimensions() { + setTestCaseTemplateName(MeshTypeName::name()); auto&& data = Data2D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -202,69 +247,57 @@ 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}); + GL::Mesh mesh = compile(T{std::move(meshData)}); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -312,7 +345,8 @@ void CompileGLTest::twoDimensions() { } } -void CompileGLTest::threeDimensions() { +template void CompileGLTest::threeDimensions() { + setTestCaseTemplateName(MeshTypeName::name()); auto&& data = Data3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -327,96 +361,77 @@ 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]); - - if(data.flags & Flag::TextureCoordinates2D) - textureCoordinates2D[0] = duplicate(indices, textureCoordinates2D[0]); + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, std::move(attributeData)}; - 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); + GL::Mesh mesh = compile(T{std::move(meshData)}, flags); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -527,6 +542,137 @@ void CompileGLTest::threeDimensions() { } } +void CompileGLTest::unknownAttribute() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::meshAttributeCustom(115), + VertexFormat::Short, nullptr}}}; + + std::ostringstream out; + Warning redirectError{&out}; + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): ignoring unknown attribute Trade::MeshAttribute::Custom(115)\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::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)