From e3c7410ac7f12a2bf072376b716094c1ac76aeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 12 Oct 2022 22:59:47 +0200 Subject: [PATCH] MeshTools: initial utility for compiling a mesh for the line shader. Currently just the bare minimum, more features such as handling multiple contiguous strips and loops inside a single mesh or an overlapping layout will come later. --- doc/snippets/MagnumShaders-gl.cpp | 9 + src/Magnum/MeshTools/CMakeLists.txt | 8 + src/Magnum/MeshTools/CompileLines.cpp | 84 +++ src/Magnum/MeshTools/CompileLines.h | 92 +++ .../MeshTools/Implementation/GenerateLines.h | 287 ++++++++ src/Magnum/MeshTools/Test/CMakeLists.txt | 37 ++ .../MeshTools/Test/CompileLinesGLTest.cpp | 368 ++++++++++ .../MeshTools/Test/CompileLinesTest.cpp | 627 ++++++++++++++++++ .../Test/CompileLinesTestFiles/bevel.tga | Bin 0 -> 443 bytes .../CompileLinesTestFiles/line-primitive.tga | Bin 0 -> 458 bytes .../Test/CompileLinesTestFiles/miter.tga | Bin 0 -> 443 bytes .../CompileLinesTestFiles/vertex-color.tga | Bin 0 -> 1811 bytes src/Magnum/Shaders/LineGL.h | 6 +- src/Magnum/Shaders/Test/LineGLTest.cpp | 2 +- 14 files changed, 1518 insertions(+), 2 deletions(-) create mode 100644 src/Magnum/MeshTools/CompileLines.cpp create mode 100644 src/Magnum/MeshTools/CompileLines.h create mode 100644 src/Magnum/MeshTools/Implementation/GenerateLines.h create mode 100644 src/Magnum/MeshTools/Test/CompileLinesGLTest.cpp create mode 100644 src/Magnum/MeshTools/Test/CompileLinesTest.cpp create mode 100644 src/Magnum/MeshTools/Test/CompileLinesTestFiles/bevel.tga create mode 100644 src/Magnum/MeshTools/Test/CompileLinesTestFiles/line-primitive.tga create mode 100644 src/Magnum/MeshTools/Test/CompileLinesTestFiles/miter.tga create mode 100644 src/Magnum/MeshTools/Test/CompileLinesTestFiles/vertex-color.tga diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index 1e72e7353..6a928ef5f 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -64,6 +64,8 @@ #ifndef MAGNUM_TARGET_GLES2 #include "Magnum/GL/TextureArray.h" +#include "Magnum/Primitives/Circle.h" +#include "Magnum/MeshTools/CompileLines.h" #include "Magnum/Shaders/DistanceFieldVector.h" #include "Magnum/Shaders/Flat.h" #include "Magnum/Shaders/Generic.h" @@ -698,6 +700,13 @@ vert.addSource(Utility::format( } #ifndef MAGNUM_TARGET_GLES2 +{ +/* [LineGL-usage] */ +Trade::MeshData circle = Primitives::circle2DWireframe(16); +GL::Mesh mesh = MeshTools::compileLines(circle); +/* [LineGL-usage] */ +} + { GL::Mesh mesh; Matrix3 transformationMatrix, projectionMatrix; diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index 338d27603..863d08bd3 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -88,6 +88,14 @@ if(MAGNUM_TARGET_GL) list(APPEND MagnumMeshTools_HEADERS Compile.h FullScreenTriangle.h) + + if(NOT MAGNUM_TARGET_GLES2) + list(APPEND MagnumMeshTools_GracefulAssert_SRCS + CompileLines.cpp) + + list(APPEND MagnumMeshTools_HEADERS + CompileLines.h) + endif() endif() # Objects shared between main and test library diff --git a/src/Magnum/MeshTools/CompileLines.cpp b/src/Magnum/MeshTools/CompileLines.cpp new file mode 100644 index 000000000..e65b2798b --- /dev/null +++ b/src/Magnum/MeshTools/CompileLines.cpp @@ -0,0 +1,84 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "CompileLines.h" + +#include "Magnum/GL/Buffer.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/MeshTools/Compile.h" +#include "Magnum/MeshTools/Implementation/GenerateLines.h" + +/* This header is included only privately and doesn't introduce any linker + dependency (taking just the PreviousPosition, NextPosition and Annotation + attribute typedefs), thus it's completely safe to not link to the Shaders + library */ +#include "Magnum/Shaders/LineGL.h" + +namespace Magnum { namespace MeshTools { + +GL::Mesh compileLines(const Trade::MeshData& lineMesh) { + Trade::MeshData mesh = Implementation::generateLines(lineMesh); + + #ifdef CORRADE_GRACEFUL_ASSERT + /* If it asserted inside, bail */ + if(!mesh.attributeCount()) return GL::Mesh{}; + #endif + + /* Upload the buffers, bind the line-specific attributes manually */ + GL::Buffer indices{GL::Buffer::TargetHint::ElementArray, mesh.indexData()}; + GL::Buffer vertices{GL::Buffer::TargetHint::Array, mesh.vertexData()}; + GL::Mesh out = compile(mesh, std::move(indices), vertices); + + /* Warn about attributes that are conflicting with line-specific attributes + and thus will get overwritten */ + static_assert(Shaders::GenericGL3D::TextureCoordinates::Location == UnsignedInt(Shaders::LineGL3D::Annotation::Location), ""); + static_assert(Shaders::GenericGL3D::Tangent::Location == UnsignedInt(Shaders::LineGL3D::PreviousPosition::Location), ""); + static_assert(Shaders::GenericGL3D::Normal::Location == UnsignedInt(Shaders::LineGL3D::NextPosition::Location), ""); + if(const Containers::Optional id = lineMesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates)) + Warning{} << "MeshTools::compileLines():" << lineMesh.attributeName(*id) << "conflicts with line annotation attribute, ignoring"; + if(const Containers::Optional id = lineMesh.findAttributeId(Trade::MeshAttribute::Tangent)) + Warning{} << "MeshTools::compileLines():" << lineMesh.attributeName(*id) << "conflicts with line previous position attribute, ignoring"; + if(const Containers::Optional id = lineMesh.findAttributeId(Trade::MeshAttribute::Normal)) + Warning{} << "MeshTools::compileLines():" << lineMesh.attributeName(*id) << "conflicts with line next position attribute, ignoring"; + + /* PreviousPosition / NextPosition are bound to the same location in both + 2D and 3D, using the 3D variant so it can be trimmed to just two + components in 2D (which wouldn't be possible the other way around) */ + out.addVertexBuffer(vertices, + mesh.attributeOffset(Implementation::MeshAttributePreviousPosition), + mesh.attributeStride(Implementation::MeshAttributePreviousPosition), + GL::DynamicAttribute{Shaders::LineGL3D::PreviousPosition{}, mesh.attributeFormat(Implementation::MeshAttributePreviousPosition)}); + out.addVertexBuffer(vertices, + mesh.attributeOffset(Implementation::MeshAttributeNextPosition), + mesh.attributeStride(Implementation::MeshAttributeNextPosition), + GL::DynamicAttribute{Shaders::LineGL3D::NextPosition{}, mesh.attributeFormat(Implementation::MeshAttributeNextPosition)}); + out.addVertexBuffer(std::move(vertices), + mesh.attributeOffset(Implementation::MeshAttributeAnnotation), + mesh.attributeStride(Implementation::MeshAttributeAnnotation), + GL::DynamicAttribute{Shaders::LineGL3D::Annotation{}, mesh.attributeFormat(Implementation::MeshAttributeAnnotation)}); + return out; +} + +}} diff --git a/src/Magnum/MeshTools/CompileLines.h b/src/Magnum/MeshTools/CompileLines.h new file mode 100644 index 000000000..f5e2633f4 --- /dev/null +++ b/src/Magnum/MeshTools/CompileLines.h @@ -0,0 +1,92 @@ +#ifndef Magnum_MeshTools_CompileLines_h +#define Magnum_MeshTools_CompileLines_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +#if defined(MAGNUM_TARGET_GL) && !defined(MAGNUM_TARGET_GLES2) +/** @file + * @brief Function @ref Magnum::MeshTools::compileLines() + * @m_since_latest + */ +#endif + +#include "Magnum/configure.h" + +#if defined(MAGNUM_TARGET_GL) && !defined(MAGNUM_TARGET_GLES2) +#include "Magnum/GL/GL.h" +#include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace MeshTools { + +/** +@brief Compile a line mesh for use with @ref Shaders::LineGL +@m_since_latest + +Returns a @ref MeshPrimitive::Triangles mesh with +@ref MeshIndexType::UnsignedInt indices, all input attributes preserved in +their original format, and additionally with +@ref Shaders::LineGL::PreviousPosition and @ref Shaders::LineGL::NextPosition +attributes added in the same format as the input +@ref Trade::MeshAttribute::Position, and the @ref Shaders::LineGL::Annotation +attribute as @ref VertexFormat::UnsignedInt, according to the +@ref Shaders-LineGL-mesh-representation documentation of the shader. + +Each line segment in the input vertices is converted to a quad, with first two +vertices inheriting vertex data from the first point of the segment and second +two vertices inheriting data from the second point of the segment. If the input +mesh is indexed, it's deindexed first. Neighbor information from a +@ref MeshPrimitive::LineStrip or @ref MeshPrimitive::LineLoop mesh is used to +form a single contiguous strip or a loop, @ref MeshPrimitive::Lines is treated +as loose segments. + +For compatibility with shaders other than @ref Shaders::LineGL, the output mesh +can be also interpreted as indexed @ref MeshPrimitive::Lines --- out of every +six indices forming a quad, two will form a line segment between the two +original points, and the remaining four collapse into two degenerate line +segments. + +Expects that the mesh contains at least a @ref Trade::MeshAttribute::Position +and is a line @relativeref{Magnum,MeshPrimitive}. + +@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. + +@requires_gles30 @ref Shaders::LineGL requires integer support in shaders which + is not available in OpenGL ES 2.0, thus neither this function is defined + in OpenGL ES 2.0 builds. +@requires_webgl20 @ref Shaders::LineGL requires integer support in shaders which + is not available in WebGL 1.0, thus neither this function is defined in + WebGL 1.0 builds. +*/ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compileLines(const Trade::MeshData& lineMesh); + +}} +#else +#error this header is available only in the desktop OpenGL, OpenGL ES 3.0+ and WebGL 2.0 builds +#endif + +#endif diff --git a/src/Magnum/MeshTools/Implementation/GenerateLines.h b/src/Magnum/MeshTools/Implementation/GenerateLines.h new file mode 100644 index 000000000..95ad388ed --- /dev/null +++ b/src/Magnum/MeshTools/Implementation/GenerateLines.h @@ -0,0 +1,287 @@ +#ifndef Magnum_MeshTools_Implementation_GenerateLines_h +#define Magnum_MeshTools_Implementation_GenerateLines_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Trade/MeshData.h" + +/* This header is included only privately and doesn't introduce any linker + dependency (taking just the LineVertexAnnotations enum), thus it's + completely safe to not link to the Shaders library */ +#include "Magnum/Shaders/Line.h" + +namespace Magnum { namespace MeshTools { namespace Implementation { namespace { + +/* This code is used internally by compileLines() and returns a MeshData + instance in order to be testable. The intention is to eventually make this + API public and the custom attributes builtin in order to make it possible to + (for example) produce glTF files that are already directly renderable as + wide line meshes. */ + +constexpr Trade::MeshAttribute MeshAttributePreviousPosition = Trade::meshAttributeCustom(32765); +constexpr Trade::MeshAttribute MeshAttributeNextPosition = Trade::meshAttributeCustom(32766); +constexpr Trade::MeshAttribute MeshAttributeAnnotation = Trade::meshAttributeCustom(32767); + +Trade::MeshData generateLines(const Trade::MeshData& lineMesh) { + CORRADE_ASSERT(lineMesh.primitive() == MeshPrimitive::Lines || + lineMesh.primitive() == MeshPrimitive::LineStrip || + lineMesh.primitive() == MeshPrimitive::LineLoop, + "Trade::MeshTools::compileLines(): expected a line primitive, got" << lineMesh.primitive(), (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /** @todo this will assert if the count in MeshData is wrong, check here + already */ + const UnsignedInt quadCount = primitiveCount(lineMesh.primitive(), lineMesh.isIndexed() ? lineMesh.indexCount() : lineMesh.vertexCount()); + + /** @todo combine this allocation with pointDuplicationIndices below, and + then reuse for the final index buffer */ + Containers::Array originalIndices; + if(lineMesh.primitive() == MeshPrimitive::Lines) { + if(lineMesh.isIndexed()) + originalIndices = lineMesh.indicesAsArray(); + } else { + if(lineMesh.primitive() == MeshPrimitive::LineStrip) { + if(lineMesh.isIndexed()) + originalIndices = generateLineStripIndices(lineMesh.indices()); + else + originalIndices = generateLineStripIndices(lineMesh.vertexCount()); + } else if(lineMesh.primitive() == MeshPrimitive::LineLoop) { + if(lineMesh.isIndexed()) + originalIndices = generateLineLoopIndices(lineMesh.indices()); + else + originalIndices = generateLineLoopIndices(lineMesh.vertexCount()); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Create a source index array for duplicate() by combining indices of a + form 00112233 (i.e., duplicating every point twice) with the original + mesh indices (if there are any) */ + Containers::Array pointDuplicationIndices{NoInit, quadCount*4}; + for(UnsignedInt i = 0; i != quadCount; ++i) { + pointDuplicationIndices[i*4 + 0] = + pointDuplicationIndices[i*4 + 1] = i*2 + 0; + pointDuplicationIndices[i*4 + 2] = + pointDuplicationIndices[i*4 + 3] = i*2 + 1; + } + if(originalIndices) + duplicateInto(pointDuplicationIndices, Containers::arrayCast<2, char>(stridedArrayView(originalIndices)), Containers::arrayCast<2, char>(stridedArrayView(pointDuplicationIndices))); + + /* Position is required, everything else is optional */ + const Containers::Optional positionAttributeId = lineMesh.findAttributeId(Trade::MeshAttribute::Position); + CORRADE_ASSERT(positionAttributeId, + "Trade::MeshTools::compileLines(): the mesh has no positions", (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Duplicate the input mesh to have each input line segment turned into + four vertices for a quad. Allocate space for the additional attributes + as well. */ + Trade::MeshData mesh = duplicate(Trade::MeshData{MeshPrimitive::Triangles, {}, pointDuplicationIndices, Trade::MeshIndexData{pointDuplicationIndices}, {}, lineMesh.vertexData(), Trade::meshAttributeDataNonOwningArray(lineMesh.attributeData()), lineMesh.vertexCount()}, { + Trade::MeshAttributeData{MeshAttributePreviousPosition, + lineMesh.attributeFormat(*positionAttributeId), nullptr}, + Trade::MeshAttributeData{MeshAttributeNextPosition, + lineMesh.attributeFormat(*positionAttributeId), nullptr}, + /** @todo use a 8-bit type and make the attribute non-interleaved to + save space? */ + Trade::MeshAttributeData{MeshAttributeAnnotation, + VertexFormat::UnsignedInt, nullptr}, + }); + + CORRADE_INTERNAL_ASSERT(mesh.attributeName(*positionAttributeId) == Trade::MeshAttribute::Position); + + /* Fill in previous/next positions, if we have any vertices at all */ + if(quadCount) { + /* Form 3D arrays where the second dimension is 2 elements */ + const Containers::StridedArrayView2D positions = mesh.attribute(Trade::MeshAttribute::Position); + const Containers::StridedArrayView3D positions3{mesh.vertexData(), static_cast(positions.data()), + {positions.size()[0]/2, 2, positions.size()[1]}, + {positions.stride()[0]*2, positions.stride()[0], positions.stride()[1]}}; + + const Containers::StridedArrayView2D previousPositions = mesh.mutableAttribute(MeshAttributePreviousPosition); + const Containers::StridedArrayView3D previousPositions3{mesh.mutableVertexData(), static_cast(previousPositions.data()), + {previousPositions.size()[0]/2, 2, previousPositions.size()[1]}, + {previousPositions.stride()[0]*2, previousPositions.stride()[0], previousPositions.stride()[1]}}; + + const Containers::StridedArrayView2D nextPositions = mesh.mutableAttribute(MeshAttributeNextPosition); + const Containers::StridedArrayView3D nextPositions3{mesh.mutableVertexData(), static_cast(nextPositions.data()), + {nextPositions.size()[0]/2, 2, nextPositions.size()[1]}, + {nextPositions.stride()[0]*2, nextPositions.stride()[0], nextPositions.stride()[1]}}; + + /* Zero-init all previous/next positions for predictable output */ + /** @todo have NoInit / ValueInit overload of duplicate(), interleave() + and interleavedLayout() instea, significantly faster than doing it + manually on sparse views after */ + { + constexpr const char Zero[sizeof(Float)*3]{}; + Containers::StridedArrayView2D zeros{Zero, positions.size(), {0, 1}}; + Utility::copy(zeros, previousPositions); + Utility::copy(zeros, nextPositions); + } + + /* Given AABBCCDDEEFF, we want to copy Position from AA__CC__EE__ to + __BB__DD__FF's PreviousPosition, and Position from __BB__DD__FF to + AA__CC__EE__'s NextPosition. Strip one group of 2 from either prefix + or suffix, pick every 2nd in the first dimension, and copy. */ + Utility::copy( + positions3.exceptSuffix(1).every(2), + previousPositions3.exceptPrefix(1).every(2)); + Utility::copy( + positions3.exceptPrefix(1).every(2), + nextPositions3.exceptSuffix(1).every(2)); + + /* Fill in previous/next neighbor positions if this is a line loop / + line strip and there's more than one quad. Given AABBCCDDEEFF, want + to copy Position from AA__CC______ to ____CC__EE__'s + PreviousPosition, and Position from ______DD__FF to __BB__DD____'s + NextPosition, and in case of loops also Position from ________EE__ + to AA__________'s PreviousPosition and Position from __BB________ to + __________FF's NextPosition. */ + /** @todo put together with the annotations once it's generalized to + the index buffer */ + if((lineMesh.primitive() == MeshPrimitive::LineStrip || + lineMesh.primitive() == MeshPrimitive::LineLoop) && quadCount > 1) { + Utility::copy( + positions3.exceptSuffix(2).every(2), + previousPositions3.exceptPrefix(2).every(2)); + Utility::copy( + positions3.exceptPrefix(3).every(2), + nextPositions3.exceptPrefix(1).exceptSuffix(2).every(2)); + } + if(lineMesh.primitive() == MeshPrimitive::LineLoop) { + Utility::copy(positions3[positions3.size()[0] - 2], previousPositions3.front()); + Utility::copy(positions3[1], nextPositions3.back()); + } + } + + /* Fill in point annotation */ + const Containers::StridedArrayView1D annotations = Containers::arrayCast(mesh.mutableAttribute(MeshAttributeAnnotation)); + for(UnsignedInt i = 0; i != quadCount; ++i) { + annotations[i*4 + 0] = Shaders::LineVertexAnnotation::Up|Shaders::LineVertexAnnotation::Begin; + annotations[i*4 + 1] = Shaders::LineVertexAnnotation::Begin; + annotations[i*4 + 2] = Shaders::LineVertexAnnotation::Up; + annotations[i*4 + 3] = {}; + } + + /* A line strip has joins everywhere except the first and last two + vertices; line loop joins also the first and last two vertices if it's + non-empty */ + /** @todo add a flag to use the original index buffer somehow to figure out + abitrary joins and loops */ + if(lineMesh.primitive() == MeshPrimitive::LineStrip || + lineMesh.primitive() == MeshPrimitive::LineLoop) { + for(UnsignedInt i = 0; i != quadCount; ++i) { + annotations[i*4 + 0] |= Shaders::LineVertexAnnotation::Join; + annotations[i*4 + 1] |= Shaders::LineVertexAnnotation::Join; + annotations[i*4 + 2] |= Shaders::LineVertexAnnotation::Join; + annotations[i*4 + 3] |= Shaders::LineVertexAnnotation::Join; + } + } + if(quadCount && lineMesh.primitive() == MeshPrimitive::LineStrip) { + annotations[0] &= ~Shaders::LineVertexAnnotation::Join; + annotations[1] &= ~Shaders::LineVertexAnnotation::Join; + annotations[quadCount*4 - 2] &= ~Shaders::LineVertexAnnotation::Join; + annotations[quadCount*4 - 1] &= ~Shaders::LineVertexAnnotation::Join; + } + + /* Create an index buffer */ + Containers::Array indexData; + arrayReserve(indexData, quadCount*6); + for(UnsignedInt i = 0; i != quadCount; ++i) { + /* The order is chosen in a way that makes it possible to interpret + the 6 indices as 3 lines instead of 2 triangles, and additionally + those forming only one line, with the other two degenerating to an + invisible point to avoid overlaps that would break blending. + + 0---2 2 + | / /| 0---2 + | / / | + |/ / | 11 32 + 1 1---3 */ + arrayAppend(indexData, { + i*4 + 2, + i*4 + 0, + i*4 + 1, + + i*4 + 1, + i*4 + 3, + i*4 + 2 + }); + + /* Add also indices for the bevel in both orientations (one will always + degenerate). For the line fallback these will all degenerate. + + 2 2 2---4 4 4-- + /| | / /| | 23 44 + / | | / / | | / + | |/ / | |/ 35 + --3 3 3---5 5 5 */ + if(i + 1 != quadCount && annotations[i*4 + 3] & Shaders::LineVertexAnnotation::Join) { + arrayAppend(indexData, { + i*4 + 2, + i*4 + 3, + i*4 + 4, + + i*4 + 4, + i*4 + 3, + i*4 + 5, + }); + } + } + + /* And finally also bevel indices between the last and first segment in + case of loops, if the loop isn't empty + + -2 -2---0 0 0- + /| | / /| | + | | / / | | + | |/ / | |/ + -1 -1 -1--1 1 */ + if(quadCount && annotations[0] & Shaders::LineVertexAnnotation::Join) { + CORRADE_INTERNAL_ASSERT(annotations[quadCount*4 - 1] & Shaders::LineVertexAnnotation::Join); + + arrayAppend(indexData, { + quadCount*4 - 2, + quadCount*4 - 1, + 0u, + + 0u, + quadCount*4 - 1, + 1u + }); + } + + Trade::MeshIndexData indices{indexData}; + return Trade::MeshData{mesh.primitive(), + indexData ? Containers::arrayAllocatorCast(std::move(indexData)) : Containers::Array{}, indices, + mesh.releaseVertexData(), mesh.releaseAttributeData()}; +} + +}}}} + +#endif diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 7dcd839a0..ae43f35da 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -43,6 +43,12 @@ corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp LIBRARIES Magnum Magnu corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) corrade_add_test(MeshToolsTransformTest TransformTest.cpp LIBRARIES MagnumMeshToolsTestLib) +if(NOT MAGNUM_TARGET_GLES2) + corrade_add_test(MeshToolsCompileLinesTest CompileLinesTest.cpp + # Needs to link to Shaders for debug output for LineVertexAnnotations + LIBRARIES MagnumMeshToolsTestLib MagnumShaders) +endif() + # Graceful assert for testing set_property(TARGET MeshToolsConcatenateTest @@ -125,4 +131,35 @@ if(MAGNUM_BUILD_GL_TESTS) add_dependencies(MeshToolsCompileGLTest TgaImporter) endif() endif() + + if(NOT MAGNUM_TARGET_GLES2) + corrade_add_test(MeshToolsCompileLinesGLTest CompileLinesGLTest.cpp + LIBRARIES + MagnumDebugTools + MagnumMeshToolsTestLib + MagnumOpenGLTester + MagnumShaders + FILES + CompileLinesTestFiles/bevel.tga + CompileLinesTestFiles/line-primitive.tga + CompileLinesTestFiles/miter.tga + CompileLinesTestFiles/vertex-color.tga) + target_include_directories(MeshToolsCompileLinesGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) + if(MAGNUM_BUILD_PLUGINS_STATIC) + if(MAGNUM_WITH_ANYIMAGEIMPORTER) + target_link_libraries(MeshToolsCompileLinesGLTest PRIVATE AnyImageImporter) + endif() + if(MAGNUM_WITH_TGAIMPORTER) + target_link_libraries(MeshToolsCompileLinesGLTest PRIVATE TgaImporter) + endif() + else() + # So the plugins get properly built when building the test + if(MAGNUM_WITH_ANYIMAGEIMPORTER) + add_dependencies(MeshToolsCompileLinesGLTest AnyImageImporter) + endif() + if(MAGNUM_WITH_TGAIMPORTER) + add_dependencies(MeshToolsCompileLinesGLTest TgaImporter) + endif() + endif() + endif() endif() diff --git a/src/Magnum/MeshTools/Test/CompileLinesGLTest.cpp b/src/Magnum/MeshTools/Test/CompileLinesGLTest.cpp new file mode 100644 index 000000000..2923b146a --- /dev/null +++ b/src/Magnum/MeshTools/Test/CompileLinesGLTest.cpp @@ -0,0 +1,368 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 +#include + +#include "Magnum/Image.h" +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/GL/Framebuffer.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Renderbuffer.h" +#include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/MeshTools/Compile.h" +#include "Magnum/MeshTools/CompileLines.h" +#include "Magnum/Shaders/FlatGL.h" +#include "Magnum/Shaders/Line.h" +#include "Magnum/Shaders/LineGL.h" +#include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/AbstractImporter.h" + +#include "configure.h" + +namespace Magnum { namespace MeshTools { namespace Test { namespace { + +struct CompileLinesGLTest: GL::OpenGLTester { + explicit CompileLinesGLTest(); + + void renderSetup(); + void renderTeardown(); + + void twoDimensions(); + void threeDimensions(); + void linePrimitiveCompatibility(); + void conflictingAttributes(); + + void emptyMesh(); + + void notLines(); + void noAttributes(); + void noPositionAttribute(); + + PluginManager::Manager _manager{"nonexistent"}; + + GL::Renderbuffer _color; + GL::Framebuffer _framebuffer{{{}, {32, 32}}}; +}; + +using namespace Math::Literals; + +const struct { + const char* name; + bool colors; + bool flip; + Shaders::LineJoinStyle joinStyle; + const char* expected; +} TwoDimensionsData[]{ + {"", false, false, Shaders::LineJoinStyle::Miter, "miter.tga"}, + {"bevel", false, false, Shaders::LineJoinStyle::Bevel, "bevel.tga"}, + {"bevel, flipped", false, true, Shaders::LineJoinStyle::Bevel, "bevel.tga"}, + {"vertex color", true, false, Shaders::LineJoinStyle::Miter, "vertex-color.tga"}, +}; + +const struct { + const char* name; + Trade::MeshAttribute attribute; + VertexFormat format; + const char* expected; +} ConflictingAttributesData[]{ + {"texture coordinates", + Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + "MeshTools::compileLines(): Trade::MeshAttribute::TextureCoordinates conflicts with line annotation attribute, ignoring\n"}, + {"tangent", + Trade::MeshAttribute::Tangent, VertexFormat::Vector3, + "MeshTools::compileLines(): Trade::MeshAttribute::Tangent conflicts with line previous position attribute, ignoring\n"}, + {"normal", + Trade::MeshAttribute::Normal, VertexFormat::Vector3, + "MeshTools::compileLines(): Trade::MeshAttribute::Normal conflicts with line next position attribute, ignoring\n"}, +}; + +CompileLinesGLTest::CompileLinesGLTest() { + addInstancedTests({&CompileLinesGLTest::twoDimensions}, + Containers::arraySize(TwoDimensionsData), + &CompileLinesGLTest::renderSetup, + &CompileLinesGLTest::renderTeardown); + + addTests({&CompileLinesGLTest::threeDimensions, + &CompileLinesGLTest::linePrimitiveCompatibility}, + &CompileLinesGLTest::renderSetup, + &CompileLinesGLTest::renderTeardown); + + addInstancedTests({&CompileLinesGLTest::conflictingAttributes}, + Containers::arraySize(ConflictingAttributesData), + &CompileLinesGLTest::renderSetup, + &CompileLinesGLTest::renderTeardown); + + addTests({&CompileLinesGLTest::emptyMesh, + + &CompileLinesGLTest::notLines, + &CompileLinesGLTest::noAttributes, + &CompileLinesGLTest::noPositionAttribute}); + + /* Load the plugins directly from the build tree. Otherwise they're either + static and already loaded or not present in the build tree */ + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef TGAIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + _color.setStorage(GL::RenderbufferFormat::RGBA8, {32, 32}); + _framebuffer + .attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) + .bind(); +} + +void CompileLinesGLTest::renderSetup() { + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + _framebuffer.clear(GL::FramebufferClear::Color); +} + +void CompileLinesGLTest::renderTeardown() {} + +void CompileLinesGLTest::twoDimensions() { + auto&& data = TwoDimensionsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Vertex { + Vector2 position; + Color3 color; + } vertexData[]{ + {{-1.0f, -1.0f}, 0xdcdcdc_rgbf}, + {{+1.0f, -1.0f}, 0xdcdcdc_rgbf}, + {{+1.0f, +1.0f}, 0x2f83cc_rgbf}, + {{-1.0f, +1.0f}, 0x2f83cc_rgbf}, + }; + auto vertices = Containers::stridedArrayView(vertexData); + + Containers::Array attributes; + arrayAppend(attributes, InPlaceInit, Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)); + if(data.colors) + arrayAppend(attributes, InPlaceInit, Trade::MeshAttribute::Color, vertices.slice(&Vertex::color)); + + GL::Mesh mesh = compileLines(Trade::MeshData{MeshPrimitive::LineLoop, {}, vertexData, std::move(attributes)}); + + Shaders::LineGL2D shader{Shaders::LineGL2D::Configuration{} + .setFlags(data.colors ? Shaders::LineGL2D::Flag::VertexColor : Shaders::LineGL2D::Flags{}) + .setJoinStyle(data.joinStyle)}; + shader + .setViewportSize({32, 32}) + .setWidth(9) + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{21.0f/32.0f}*Vector2::yScale(data.flip ? -1.0f : 1.0f))) + .draw(mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join({MESHTOOLS_TEST_DIR, "CompileLinesTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} + +void CompileLinesGLTest::threeDimensions() { + /* Same as the initial case in twoDimensions(), just in 3D and with a + varying Z (which shouldn't have any effect as it's rendering in an + orthographic projection) */ + + Vector3 positions[]{ + {-1.0f, -1.0f, +0.5f}, + {+1.0f, -1.0f, +0.5f}, + {+1.0f, +1.0f, -0.5f}, + {-1.0f, +1.0f, -0.5f}, + }; + + GL::Mesh mesh = compileLines(Trade::MeshData{MeshPrimitive::LineLoop, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(positions)} + }}); + + Shaders::LineGL3D{} + .setViewportSize({32, 32}) + .setWidth(9) + .setTransformationProjectionMatrix(Matrix4::scaling({Vector2{21.0f/32.0f}, 1.0f})) + .draw(mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join(MESHTOOLS_TEST_DIR, "CompileLinesTestFiles/miter.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +void CompileLinesGLTest::linePrimitiveCompatibility() { + struct Vertex { + Vector2 position; + Color3 color; + } vertexData[]{ + {{-1.0f, -1.0f}, 0xdcdcdc_rgbf}, + {{+1.0f, -1.0f}, 0xdcdcdc_rgbf}, + {{+1.0f, +1.0f}, 0x2f83cc_rgbf}, + {{-1.0f, +1.0f}, 0x2f83cc_rgbf}, + }; + auto vertices = Containers::stridedArrayView(vertexData); + + /* Enabling blending and a half-transparent color to catch accidental + overlaps where they shouldn't be */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + Trade::MeshData lineMeshData{MeshPrimitive::LineLoop, {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, vertices.slice(&Vertex::color)}, + }}; + + Shaders::FlatGL2D shader{Shaders::FlatGL2D::Configuration{} + .setFlags(Shaders::FlatGL2D::Flag::VertexColor)}; + shader + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{21.0f/32.0f})) + .setColor(0x80808080_rgbaf); + + /* Render the original */ + shader.draw(compile(lineMeshData)); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join(MESHTOOLS_TEST_DIR, "CompileLinesTestFiles/line-primitive.tga"), + (DebugTools::CompareImageToFile{_manager})); + + _framebuffer.clear(GL::FramebufferClear::Color); + + /* Render the line mesh with the primitive set back to lines. The index + buffer layout should be compatible with it, and produce the same + result. */ + shader.draw(compileLines(lineMeshData).setPrimitive(MeshPrimitive::Lines)); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join(MESHTOOLS_TEST_DIR, "CompileLinesTestFiles/line-primitive.tga"), + (DebugTools::CompareImageToFile{_manager})); + + GL::Renderer::disable(GL::Renderer::Feature::Blending); +} + +void CompileLinesGLTest::conflictingAttributes() { + auto&& data = ConflictingAttributesData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Same as the initial case in twoDimensions(), should just warn but + produce correct result */ + + struct Vertex { + Vector3 position; + Vector3 extra; + } vertexData[]{ + {{-1.0f, -1.0f, +0.5f}, {}}, + {{+1.0f, -1.0f, +0.5f}, {}}, + {{+1.0f, +1.0f, -0.5f}, {}}, + {{-1.0f, +1.0f, -0.5f}, {}}, + }; + auto vertices = Containers::stridedArrayView(vertexData); + + Trade::MeshData lineMesh{MeshPrimitive::LineLoop, {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)}, + Trade::MeshAttributeData{data.attribute, data.format, vertices.slice(&Vertex::extra)}, + }}; + + std::ostringstream out; + GL::Mesh mesh{NoCreate}; + { + Warning redirectWarning{&out}; + mesh = compileLines(lineMesh); + } + + Shaders::LineGL3D{} + .setViewportSize({32, 32}) + .setWidth(9) + .setTransformationProjectionMatrix(Matrix4::scaling({Vector2{21.0f/32.0f}, 1.0f})) + .draw(mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join(MESHTOOLS_TEST_DIR, "CompileLinesTestFiles/miter.tga"), + (DebugTools::CompareImageToFile{_manager})); + CORRADE_COMPARE(out.str(), data.expected); +} + +void CompileLinesGLTest::emptyMesh() { + GL::Mesh mesh = compileLines(Trade::MeshData{MeshPrimitive::LineLoop, {}, nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector2, nullptr} + }}); + + CORRADE_COMPARE(mesh.primitive(), GL::MeshPrimitive::Triangles); + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.count(), 0); +} + +void CompileLinesGLTest::notLines() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Vector3 positions[3]{}; + + std::ostringstream out; + Error redirectError{&out}; + compileLines(Trade::MeshData{MeshPrimitive::TriangleFan, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(positions)} + }}); + CORRADE_COMPARE(out.str(), "Trade::MeshTools::compileLines(): expected a line primitive, got MeshPrimitive::TriangleFan\n"); +} + +void CompileLinesGLTest::noAttributes() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + compileLines(Trade::MeshData{MeshPrimitive::Lines, 12}); + CORRADE_COMPARE(out.str(), "Trade::MeshTools::compileLines(): the mesh has no positions\n"); +} + +void CompileLinesGLTest::noPositionAttribute() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Vector3 colors[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + compileLines(Trade::MeshData{MeshPrimitive::Lines, {}, colors, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, Containers::stridedArrayView(colors)} + }}); + CORRADE_COMPARE(out.str(), "Trade::MeshTools::compileLines(): the mesh has no positions\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CompileLinesGLTest) diff --git a/src/Magnum/MeshTools/Test/CompileLinesTest.cpp b/src/Magnum/MeshTools/Test/CompileLinesTest.cpp new file mode 100644 index 000000000..a4bf5472a --- /dev/null +++ b/src/Magnum/MeshTools/Test/CompileLinesTest.cpp @@ -0,0 +1,627 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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/Math/PackingBatch.h" +#include "Magnum/MeshTools/Implementation/GenerateLines.h" + +namespace Magnum { namespace MeshTools { namespace Test { namespace { + +struct CompileLinesTest: TestSuite::Tester { + explicit CompileLinesTest(); + + template void oneLoop(); + + void extraAttributes(); + void zeroVertices(); + void twoVerticesStrip(); + void twoVerticesLoop(); + /* Non-line primitives and absence of position attribute tested in + CompileLinesGLTest, to verify it's not accessed earlier than the + assertion */ +}; + +using namespace Math::Literals; + +const struct { + const char* name; + MeshPrimitive primitive; + Containers::Array positions; + Containers::Array indices; + bool expectedJoins; + bool expectedJoinsFirstLast; +} OneLoopData[]{ + {"loose segments", MeshPrimitive::Lines, {InPlaceInit, { + {-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, false, false}, + {"loose indexed segments", MeshPrimitive::Lines, {InPlaceInit, { + {-1.0f, -1.0f}, + {+1.0f, -1.0f}, + {+1.0f, +1.0f}, + {-1.0f, +1.0f}, + }}, {InPlaceInit, { + 0, 1, 1, 2, 2, 3, 3, 0 + }}, false, false}, + /** @todo indexed segments that get connected */ + {"loop", MeshPrimitive::LineLoop, {InPlaceInit, { + {-1.0f, -1.0f}, + {+1.0f, -1.0f}, + {+1.0f, +1.0f}, + {-1.0f, +1.0f}, + }}, nullptr, true, true}, + {"indexed loop", MeshPrimitive::LineLoop, {InPlaceInit, { + {-1.0f, -1.0f}, + {-1.0f, +1.0f}, + {+1.0f, -1.0f}, + {+1.0f, +1.0f}, + }}, {InPlaceInit, { + 0, 2, 3, 1 + }}, true, true}, + {"strip", MeshPrimitive::LineStrip, {InPlaceInit, { + {-1.0f, -1.0f}, + {+1.0f, -1.0f}, + {+1.0f, +1.0f}, + {-1.0f, +1.0f}, + {-1.0f, -1.0f}, + }}, nullptr, true, false}, + {"indexed strip", MeshPrimitive::LineStrip, {InPlaceInit, { + {-1.0f, -1.0f}, + {-1.0f, +1.0f}, + {+1.0f, -1.0f}, + {+1.0f, +1.0f}, + }}, {InPlaceInit, { + 0, 2, 3, 1, 0 + }}, true, false}, + /** @todo closed (indexed) strip, once arbitrary index buffer looping is supported */ +}; + +CompileLinesTest::CompileLinesTest() { + addInstancedTests({ + &CompileLinesTest::oneLoop, + &CompileLinesTest::oneLoop, + &CompileLinesTest::oneLoop}, + Containers::arraySize(OneLoopData)); + + addTests({&CompileLinesTest::extraAttributes, + &CompileLinesTest::zeroVertices, + &CompileLinesTest::twoVerticesStrip, + &CompileLinesTest::twoVerticesLoop}); +} + +template void CompileLinesTest::oneLoop() { + auto&& data = OneLoopData[testCaseInstanceId()]; + setTestCaseTemplateName(Math::TypeTraits::name()); + setTestCaseDescription(data.name); + + Containers::Array indices{NoInit, data.indices.size()}; + Math::castInto(stridedArrayView(data.indices), stridedArrayView(indices)); + + Trade::MeshData lineMesh{data.primitive, + {}, indices, indices ? Trade::MeshIndexData{indices} : Trade::MeshIndexData{}, + {}, data.positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, stridedArrayView(data.positions)} + }}; + + Trade::MeshData mesh = Implementation::generateLines(lineMesh); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(mesh.attributeCount(), 4); + + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedInt); + if(data.expectedJoins && data.expectedJoinsFirstLast) + CORRADE_COMPARE_AS(mesh.indices(), Containers::arrayView({ + 2, 0, 1, 1, 3, 2, + 2, 3, 4, 4, 3, 5, /* join */ + 6, 4, 5, 5, 7, 6, + 6, 7, 8, 8, 7, 9, /* join */ + 10, 8, 9, 9, 11, 10, + 10, 11, 12, 12, 11, 13, /* join */ + 14, 12, 13, 13, 15, 14, + 14, 15, 0, 0, 15, 1, /* join */ + }), TestSuite::Compare::Container); + else if(data.expectedJoins) + CORRADE_COMPARE_AS(mesh.indices(), Containers::arrayView({ + 2, 0, 1, 1, 3, 2, + 2, 3, 4, 4, 3, 5, /* join */ + 6, 4, 5, 5, 7, 6, + 6, 7, 8, 8, 7, 9, /* join */ + 10, 8, 9, 9, 11, 10, + 10, 11, 12, 12, 11, 13, /* join */ + 14, 12, 13, 13, 15, 14, + }), TestSuite::Compare::Container); + else + CORRADE_COMPARE_AS(mesh.indices(), Containers::arrayView({ + 2, 0, 1, 1, 3, 2, + 6, 4, 5, 5, 7, 6, + 10, 8, 9, 9, 11, 10, + 14, 12, 13, 13, 15, 14, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::Position)); + CORRADE_COMPARE(mesh.attributeFormat(Trade::MeshAttribute::Position), VertexFormat::Vector2); + Containers::StridedArrayView1D positions = mesh.attribute(Trade::MeshAttribute::Position); + CORRADE_COMPARE_AS(positions, 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}, + {+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} + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeNextPosition)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeAnnotation)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributePreviousPosition), VertexFormat::Vector2); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeNextPosition), VertexFormat::Vector2); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeAnnotation), VertexFormat::UnsignedInt); + if(data.expectedJoins && data.expectedJoinsFirstLast) { + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributePreviousPosition), Containers::arrayView({ + positions[12], positions[12], + positions[0], positions[0], + positions[0], positions[0], + positions[4], positions[4], + positions[4], positions[4], + positions[8], positions[8], + positions[8], positions[8], + positions[12], positions[12], + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributeNextPosition), Containers::arrayView({ + positions[2], positions[2], + positions[6], positions[6], + positions[6], positions[6], + positions[10], positions[10], + positions[10], positions[10], + positions[14], positions[14], + positions[14], positions[14], + positions[2], positions[2], + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const Shaders::LineVertexAnnotations>(mesh.attribute(Implementation::MeshAttributeAnnotation))), Containers::arrayView({ + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + }), TestSuite::Compare::Container); + } else if(data.expectedJoins) { + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributePreviousPosition), Containers::arrayView({ + {}, {}, + positions[0], positions[0], + positions[0], positions[0], + positions[4], positions[4], + positions[4], positions[4], + positions[8], positions[8], + positions[8], positions[8], + positions[12], positions[12], + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributeNextPosition), Containers::arrayView({ + positions[2], positions[2], + positions[6], positions[6], + positions[6], positions[6], + positions[10], positions[10], + positions[10], positions[10], + positions[14], positions[14], + positions[14], positions[14], + {}, {} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const Shaders::LineVertexAnnotations>(mesh.attribute(Implementation::MeshAttributeAnnotation))), Containers::arrayView({ + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up, + {}, + }), TestSuite::Compare::Container); + } else { + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributePreviousPosition), Containers::arrayView({ + {}, {}, + positions[0], positions[0], + {}, {}, + positions[4], positions[4], + {}, {}, + positions[8], positions[8], + {}, {}, + positions[12], positions[12], + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributeNextPosition), Containers::arrayView({ + positions[2], positions[2], + {}, {}, + positions[6], positions[6], + {}, {}, + positions[10], positions[10], + {}, {}, + positions[14], positions[14], + {}, {}, + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const Shaders::LineVertexAnnotations>(mesh.attribute(Implementation::MeshAttributeAnnotation))), Containers::arrayView({ + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Up, + {}, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Up, + {}, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Up, + {}, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Up, + {}, + }), TestSuite::Compare::Container); + } +} + +void CompileLinesTest::extraAttributes() { + const struct Vertex { + Color3ub color; + Vector3b position; + UnsignedShort objectId; + } vertexData[]{ + {0xdcdcdc_rgb, {-1, -1, 0}, 156}, + {0xdcdcdc_rgb, {+1, -1, 1}, 223}, + {0x2f83cc_rgb, {+1, +1, 0}, 999}, + {0x2f83cc_rgb, {-1, +1, 1}, 768} + }; + auto vertices = Containers::stridedArrayView(vertexData); + + Trade::MeshData lineMesh{MeshPrimitive::LineLoop, + {}, vertexData, { + /* Having position not first to catch accidental use of first + attribute as position */ + Trade::MeshAttributeData{Trade::MeshAttribute::Color, vertices.slice(&Vertex::color)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, vertices.slice(&Vertex::objectId)}, + }}; + + Trade::MeshData mesh = Implementation::generateLines(lineMesh); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(mesh.attributeCount(), 6); + + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(mesh.indices(), Containers::arrayView({ + 2, 0, 1, 1, 3, 2, + 2, 3, 4, 4, 3, 5, /* join */ + 6, 4, 5, 5, 7, 6, + 6, 7, 8, 8, 7, 9, /* join */ + 10, 8, 9, 9, 11, 10, + 10, 11, 12, 12, 11, 13, /* join */ + 14, 12, 13, 13, 15, 14, + 14, 15, 0, 0, 15, 1, /* join */ + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::Position)); + CORRADE_COMPARE(mesh.attributeFormat(Trade::MeshAttribute::Position), VertexFormat::Vector3b); + Containers::StridedArrayView1D positions = mesh.attribute(Trade::MeshAttribute::Position); + CORRADE_COMPARE_AS(positions, Containers::arrayView({ + {-1, -1, 0}, {-1, -1, 0}, + {+1, -1, 1}, {+1, -1, 1}, + {+1, -1, 1}, {+1, -1, 1}, + {+1, +1, 0}, {+1, +1, 0}, + {+1, +1, 0}, {+1, +1, 0}, + {-1, +1, 1}, {-1, +1, 1}, + {-1, +1, 1}, {-1, +1, 1}, + {-1, -1, 0}, {-1, -1, 0} + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::Color)); + CORRADE_COMPARE(mesh.attributeFormat(Trade::MeshAttribute::Color), VertexFormat::Vector3ubNormalized); + CORRADE_COMPARE_AS(mesh.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ + 0xdcdcdc_rgb, 0xdcdcdc_rgb, + 0xdcdcdc_rgb, 0xdcdcdc_rgb, + 0xdcdcdc_rgb, 0xdcdcdc_rgb, + 0x2f83cc_rgb, 0x2f83cc_rgb, + 0x2f83cc_rgb, 0x2f83cc_rgb, + 0x2f83cc_rgb, 0x2f83cc_rgb, + 0x2f83cc_rgb, 0x2f83cc_rgb, + 0xdcdcdc_rgb, 0xdcdcdc_rgb, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::ObjectId)); + CORRADE_COMPARE(mesh.attributeFormat(Trade::MeshAttribute::ObjectId), VertexFormat::UnsignedShort); + CORRADE_COMPARE_AS(mesh.attribute(Trade::MeshAttribute::ObjectId), Containers::arrayView({ + 156, 156, + 223, 223, + 223, 223, + 999, 999, + 999, 999, + 768, 768, + 768, 768, + 156, 156, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeNextPosition)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeAnnotation)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributePreviousPosition), VertexFormat::Vector3b); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeNextPosition), VertexFormat::Vector3b); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeAnnotation), VertexFormat::UnsignedInt); + + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributePreviousPosition), Containers::arrayView({ + positions[12], positions[12], + positions[0], positions[0], + positions[0], positions[0], + positions[4], positions[4], + positions[4], positions[4], + positions[8], positions[8], + positions[8], positions[8], + positions[12], positions[12], + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributeNextPosition), Containers::arrayView({ + positions[2], positions[2], + positions[6], positions[6], + positions[6], positions[6], + positions[10], positions[10], + positions[10], positions[10], + positions[14], positions[14], + positions[14], positions[14], + positions[2], positions[2], + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const Shaders::LineVertexAnnotations>(mesh.attribute(Implementation::MeshAttributeAnnotation))), Containers::arrayView({ + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + }), TestSuite::Compare::Container); +} + +void CompileLinesTest::zeroVertices() { + Trade::MeshData lineMesh{MeshPrimitive::LineLoop, nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3usNormalized, nullptr} + }}; + + Trade::MeshData mesh = Implementation::generateLines(lineMesh); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(mesh.attributeCount(), 4); + CORRADE_COMPARE(mesh.vertexCount(), 0); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::Position)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeAnnotation)); +} + +void CompileLinesTest::twoVerticesStrip() { + Vector2 positionData[]{ + {-1.0f, 0.0f}, + {+1.0f, 0.0f} + }; + + Trade::MeshData lineMesh{MeshPrimitive::LineStrip, {}, positionData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(positionData)} + }}; + + Trade::MeshData mesh = Implementation::generateLines(lineMesh); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(mesh.attributeCount(), 4); + + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(mesh.indices(), Containers::arrayView({ + 2, 0, 1, 1, 3, 2, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::Position)); + CORRADE_COMPARE(mesh.attributeFormat(Trade::MeshAttribute::Position), VertexFormat::Vector2); + Containers::StridedArrayView1D positions = mesh.attribute(Trade::MeshAttribute::Position); + CORRADE_COMPARE_AS(positions, Containers::arrayView({ + {-1.0f, 0.0f}, {-1.0f, 0.0f}, + {+1.0f, 0.0f}, {+1.0f, 0.0f}, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributePreviousPosition), VertexFormat::Vector2); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributePreviousPosition), Containers::arrayView({ + {}, {}, + positions[0], positions[0], + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeNextPosition), VertexFormat::Vector2); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributeNextPosition), Containers::arrayView({ + positions[2], positions[2], + {}, {}, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeAnnotation)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeAnnotation), VertexFormat::UnsignedInt); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const Shaders::LineVertexAnnotations>(mesh.attribute(Implementation::MeshAttributeAnnotation))), Containers::arrayView({ + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Begin, + Shaders::LineVertexAnnotation::Up, + {}, + }), TestSuite::Compare::Container); +} + +void CompileLinesTest::twoVerticesLoop() { + Vector2 positionData[]{ + {-1.0f, 0.0f}, + {+1.0f, 0.0f} + }; + + Trade::MeshData lineMesh{MeshPrimitive::LineLoop, {}, positionData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(positionData)} + }}; + + Trade::MeshData mesh = Implementation::generateLines(lineMesh); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(mesh.attributeCount(), 4); + + CORRADE_VERIFY(mesh.isIndexed()); + CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(mesh.indices(), Containers::arrayView({ + 2, 0, 1, 1, 3, 2, + 2, 3, 4, 4, 3, 5, /* join */ + 6, 4, 5, 5, 7, 6, + 6, 7, 0, 0, 7, 1, /* join */ + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Trade::MeshAttribute::Position)); + CORRADE_COMPARE(mesh.attributeFormat(Trade::MeshAttribute::Position), VertexFormat::Vector2); + Containers::StridedArrayView1D positions = mesh.attribute(Trade::MeshAttribute::Position); + CORRADE_COMPARE_AS(positions, Containers::arrayView({ + {-1.0f, 0.0f}, {-1.0f, 0.0f}, + {+1.0f, 0.0f}, {+1.0f, 0.0f}, + {+1.0f, 0.0f}, {+1.0f, 0.0f}, + {-1.0f, 0.0f}, {-1.0f, 0.0f}, + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributePreviousPosition), VertexFormat::Vector2); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributePreviousPosition), Containers::arrayView({ + positions[4], positions[4], + positions[0], positions[0], + positions[0], positions[0], + positions[4], positions[4], + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributePreviousPosition)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeNextPosition), VertexFormat::Vector2); + CORRADE_COMPARE_AS(mesh.attribute(Implementation::MeshAttributeNextPosition), Containers::arrayView({ + positions[2], positions[2], + positions[6], positions[6], + positions[6], positions[6], + positions[2], positions[2], + }), TestSuite::Compare::Container); + + CORRADE_VERIFY(mesh.hasAttribute(Implementation::MeshAttributeAnnotation)); + CORRADE_COMPARE(mesh.attributeFormat(Implementation::MeshAttributeAnnotation), VertexFormat::UnsignedInt); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const Shaders::LineVertexAnnotations>(mesh.attribute(Implementation::MeshAttributeAnnotation))), Containers::arrayView({ + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Begin| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Up| + Shaders::LineVertexAnnotation::Join, + Shaders::LineVertexAnnotation::Join, + }), TestSuite::Compare::Container); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CompileLinesTest) diff --git a/src/Magnum/MeshTools/Test/CompileLinesTestFiles/bevel.tga b/src/Magnum/MeshTools/Test/CompileLinesTestFiles/bevel.tga new file mode 100644 index 0000000000000000000000000000000000000000..584d131c3d65ef54bee5f1a9ac7c81e32e64ba60 GIT binary patch literal 443 zcmds!F$#b{3F8%CFB-djAWS#%5e<&m@9L!Q!(S7wmd4<5J6_Eldh_ z3C+0F9;YQt3U-{Cal!UuGcL7sH-JgOHhnWLwfnjUlY)g^GcMSwW5%VHEZQ(B*t})N d1)Da_xYTZw222Vzs+)0@mdC>yk=)}e>jyhwx8eW* literal 0 HcmV?d00001 diff --git a/src/Magnum/MeshTools/Test/CompileLinesTestFiles/miter.tga b/src/Magnum/MeshTools/Test/CompileLinesTestFiles/miter.tga new file mode 100644 index 0000000000000000000000000000000000000000..a558be9d6f2b3f2f2866aca5db2e91ad3dbf4fe0 GIT binary patch literal 443 ucmZQz;9`IQ1qKjYATKZff9`)EXru+J17vbH$Yd}JY{zgGqe)1D;|Kt}mJo3O literal 0 HcmV?d00001 diff --git a/src/Magnum/MeshTools/Test/CompileLinesTestFiles/vertex-color.tga b/src/Magnum/MeshTools/Test/CompileLinesTestFiles/vertex-color.tga new file mode 100644 index 0000000000000000000000000000000000000000..7f70539a652973a3fb78c4f07f97e0ad8ec6206e GIT binary patch literal 1811 zcmajg&r2Io5C`zx&BloTMEnmtc`AjTdh*muLFlO`FFgr@rv_1It%OuV3?Zd0f+A>X zY0)Amh5mrn#MLx0G5KGn`OdtNx6j>ea@o8$^Lg`T7UFqRo_?l1eBTy}MQV;lBP!v^ z8xDHZz)x@3i)78m6V~fk_lMm!-E{uQUVtavuVFvH{-AY6H_;D?$>GV4t0FF^af9Dq zIiAlCHPepYM0}|6{huGH|LZd$k8|=WxaU-mm$&n-zwJ@~`yuCZ?u1H7=aysl!MUaD z)A!`7!a_m4v??~B6k=73k=}8QdM8Z+2#>8vm9wf^$DAfzuY0sb-7l4xYfN)OYMtYG ziHRQImZ%Bd-w&(Q{j?r)#k4C=av_Q7dYyykcU)sS9dTVqV%qg}_Liu#zf7(xrrQy_ zzgn4?dS7@~JJ0B9ckzyEOs69{LDZ|2vFW3K@Jg5O7AQi&rMhA!by>Tnm5B+j zU4EG98Z%*fx1BSUiP?I2pW3e;WV&LamRP5C62^mM%(Yo+y||a@vRQB^sQ1N9;cc9+ z%uu2AACE0x-w1xw0_{JPGL(rdg@wF5e>=Z|{CrwI=Z3d)wz{#2yf7(`b8`BA=xwWr r&$;m&8|d6|pBmx struct Vertex { VectorTypeFor previousPosition;