From 42efcddfbada1f3526f5e4b0ef9baa45e2db2315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 21 Oct 2022 12:38:58 +0200 Subject: [PATCH] [wip more] --- .../MeshTools/Test/GenerateLinesTest.cpp | 2 + src/Magnum/Shaders/FlatGL.cpp | 10 +- src/Magnum/Shaders/Line.frag | 7 +- src/Magnum/Shaders/Line.vert | 85 ++++--- src/Magnum/Shaders/Test/LineGLTest.cpp | 212 +++++++++++++++--- .../joint-angles-acute-overlapping-cap.tga | Bin 0 -> 1008 bytes .../joint-angles-acute-round-caps.tga | Bin 0 -> 3062 bytes .../Test/LineTestFiles/joint-angles-acute.tga | Bin 0 -> 930 bytes .../joint-angles-obtuse-round-caps.tga | Bin 0 -> 3524 bytes .../LineTestFiles/joint-angles-obtuse.tga | Bin 1066 -> 1066 bytes .../joint-angles-short-cap-acute-round.tga | Bin 0 -> 3063 bytes .../joint-angles-short-cap-acute.tga | Bin 0 -> 1008 bytes .../LineTestFiles/joint-angles-short-cap.tga | Bin 0 -> 4728 bytes .../Test/LineTestFiles/line-caps-round.tga | Bin 0 -> 2162 bytes .../LineTestFiles/line-caps-square-flat.tga | Bin 0 -> 738 bytes .../Test/LineTestFiles/line-caps-square.tga | Bin 0 -> 1748 bytes .../Test/LineTestFiles/line-caps-triangle.tga | Bin 0 -> 1470 bytes 17 files changed, 243 insertions(+), 73 deletions(-) create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute-overlapping-cap.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute-round-caps.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-obtuse-round-caps.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap-acute-round.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap-acute.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/line-caps-round.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/line-caps-square-flat.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/line-caps-square.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/line-caps-triangle.tga diff --git a/src/Magnum/MeshTools/Test/GenerateLinesTest.cpp b/src/Magnum/MeshTools/Test/GenerateLinesTest.cpp index 0baeae421..f7371cf92 100644 --- a/src/Magnum/MeshTools/Test/GenerateLinesTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateLinesTest.cpp @@ -25,6 +25,8 @@ #include +#include "Magnum/MeshTools/GenerateLines.h" + namespace Magnum { namespace MeshTools { namespace Test { namespace { struct GenerateLinesTest: TestSuite::Tester { diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 21483822e..240e46dd3 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -164,7 +164,9 @@ template typename FlatGL::CompileState FlatG } #endif vert.addSource(rs.getString("generic.glsl")) - .addSource(rs.getString("Flat.vert")); + .addSource(rs.getString("Flat.vert")) + .submitCompile(); + frag.addSource(flags & Flag::Textured ? "#define TEXTURED\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(flags & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") @@ -189,10 +191,8 @@ template typename FlatGL::CompileState FlatG } #endif frag.addSource(rs.getString("generic.glsl")) - .addSource(rs.getString("Flat.frag")); - - vert.submitCompile(); - frag.submitCompile(); + .addSource(rs.getString("Flat.frag")) + .submitCompile(); FlatGL out{NoInit}; out._flags = flags; diff --git a/src/Magnum/Shaders/Line.frag b/src/Magnum/Shaders/Line.frag index aa507406a..d2ead3e96 100644 --- a/src/Magnum/Shaders/Line.frag +++ b/src/Magnum/Shaders/Line.frag @@ -176,17 +176,15 @@ void main() { lowp const vec4 color = materials[materialId].color; #endif + // TODO this comment is plain wrong /* Pixels with `abs(centerDistanceSigned) <= [d+w,w]` are foreground, pixels with `abs(centerDistanceSigned) > [d+w+s,w+s]` are background, smoothstep in between */ highp const vec2 edge = vec2(halfSegmentLength+0.5*width, width*0.5); -// lowp const vec2 factor = smoothstep( // TODO CSE -// edge - vec2(smoothness), -// edge + vec2(smoothness), abs(centerDistanceSigned)); -// lowp const vec2 factor = step(edge, abs(centerDistanceSigned)); // TODO better names ffs highp vec2 distance_ = vec2(max(abs(centerDistanceSigned.x) - halfSegmentLength, 0.0), abs(centerDistanceSigned.y)); + // TODO document what is this if(hasCap < 0.0) distance_.x = 0.0; #ifdef CAP_STYLE_SQUARE @@ -207,7 +205,6 @@ void main() { const highp float factorX = factor.x; #endif -// fragmentColor.ba = vec2(0.0); fragmentColor = mix( #ifdef VERTEX_COLOR interpolatedVertexColor* diff --git a/src/Magnum/Shaders/Line.vert b/src/Magnum/Shaders/Line.vert index 5f0a00d09..556c34559 100644 --- a/src/Magnum/Shaders/Line.vert +++ b/src/Magnum/Shaders/Line.vert @@ -194,6 +194,7 @@ in highp mat4 instancedTransformationMatrix; /* Outputs */ +// TODO document, maybe join together? out highp vec2 centerDistanceSigned; out highp float halfSegmentLength; out lowp float hasCap; @@ -264,12 +265,14 @@ void main() { highp const vec2 neighborDirection = (gl_VertexID & 2) == 0 ? transformedPosition - transformedPreviousPosition : transformedNextPosition - transformedPosition; + highp const vec2 firstPoint = (gl_VertexID & 2) == 0 ? + transformedPosition : transformedPreviousPosition; + highp const vec2 neighborEndPoint = (gl_VertexID & 2) == 0 ? + transformedPreviousPosition : transformedNextPosition; highp const float directionLength = length(direction); - // TODO since this is used just for AA, set to a special value in case of - // a line joint that shouldn't be AAd? halfSegmentLength = length(direction*0.5*viewportSize/2.0); - // TODO zero-sized lines better? + // TODO zero-sized lines better? average from prev/next? highp const vec2 directionNormalized = directionLength == 0.0 ? vec2(1.0, 0.0) : direction/directionLength; /* Line width includes also twice the smoothness radius, some extra padding @@ -282,7 +285,7 @@ void main() { position is always either `A` or `B` for all four quad corners, the `d` comes in the direction attribute, `pd`/`nd` in neighborDirection and the vertex order, which is (4n +) 0/1/2/3, in gl_VertexID. - + // TODO redo all this here, the whole comment is outdated 0-d->-------2-d-> | / \ A---------B nd @@ -291,7 +294,6 @@ void main() { \ \ . nd . . v C . */ - // TODO redo all this here /* The perpendicular direction is rotating 90° counterclockwise. Which means for points 1 and 3 (i.e., gl_VertexID not divisible by 2) we need to negate it to point the other way. */ @@ -337,61 +339,70 @@ void main() { For points 0 and 1 it'll be in the negative direction `d`, for points 2 and 3 in positive `d`. */ - // TODO make the 0.7 configurable - if(all(isnan(neighborDirection)) || dot(normalize(direction), normalize(neighborDirection)) < -0.7) { + if(all(isnan(neighborDirection)) || + /* Cap limit */ + // TODO make the 0.7 configurable; no actually drop it altogether + dot(normalize(direction), normalize(neighborDirection)) < -0.99 || + /* Neighbor segment too short */ + // TODO why the 2*?? why the square?? + (abs(dot(perpendicular(normalize(direction))*viewportSize/2.0, (neighborEndPoint - firstPoint)*viewportSize/2.0)) < 2*edgeDistance*edgeDistance && + // TODO this is a wrong attempt to handle colinear, needs to + // calculate proper distance from a line segment instead or do + // something else entirely ffs + dot(direction, neighborDirection) <= 0.0) + ) { edgeDirection = (directionNormalized*capSign + perpendicular(directionNormalized)*edgeSign)*edgeDistance*2.0/viewportSize ; centerDistanceSigned.x = (edgeDistance + halfSegmentLength)*capSign; - hasCap = 1.0; // TODO uhhhhhhh some extra offset for the cap here?? /* Otherwise we need to create a tight joint with the neighboring line segment, as shown with the points 2 and 3. Given normalized direction `d` and neighbor direction `nd`, `normalized(d + nd)` is the "average" direction of the two and `perpendicular(normalized(d + nd))` gives us - the direction from B to 2: - - --------+----2 - | / alpha/2 - w | / j - |/ - --d->-B - alpha/2 / \ - / nd - / v - ----3 - - With `alpha` being the angle between `d` and `nd`, `alpha/2` appears in - two right triangles and the following holds, `w` being the edge distance - from above, and `j` being the length that's needed to scale `perpendicular(normalized(d + nd))` to get point 2: - - |d + nd| w 2 w - sin(alpha/2) = -------- = --- --> j = -------- - 2 |d| j |d + nd| + the direction from B to 2 (or from 3 to B): + + --------+---2 + | α/ + w | / j + |/ + +_-----d->-B + -_ α/α\ + -_ / nd + d + nd /-_ v + ----3 -_ \ + -+ + + With `2α` being the angle between `d` and `nd`, `α` appears in two right + triangles and the following holds, `w` being the edge distance from + above, and `j` being the length that's needed to scale + `perpendicular(normalized(d + nd))` to get point 2: + + |d + nd| w 2 w + sin(α) = -------- = --- --> j = -------- + 2 |d| j |d + nd| Point 3 is then just in the opposite direction; for the other side it's done equivalently. */ } else { -// edgeDirection = perpendicular(directionNormalized)*edgeSign*edgeDistance*2.0/viewportSize; - const highp vec2 averageDirection = capSign*(directionNormalized + normalize(neighborDirection)); const highp float averageDirectionLength = length(averageDirection); const highp float j = 2.0*edgeDistance/averageDirectionLength; edgeDirection = (normalize(perpendicular(averageDirection))*capSign*edgeSign*j)*2.0/viewportSize; -// const float ex = sqrt((4.0*edgeDistance*edgeDistance/(averageDirectionLength*averageDirectionLength)) - edgeDistance*edgeDistance); const highp float ex = sqrt(j*j - edgeDistance*edgeDistance); -// edgeDirection = -perpendicular(averageDirection)*(1.0*edgeDistance*edgeSign*capSign/averageDirectionLength) -// *2.0/viewportSize; - - // TODO the ex should be included in here, why it isn't?? - centerDistanceSigned.x = halfSegmentLength*capSign;// + ex*sign(dot(direction*capSign, (perpendicular(averageDirection))*capSign*edgeSign)); - - hasCap = -1.0; // TODO uhhh some extra offset here so 0 is in the center? + centerDistanceSigned.x = halfSegmentLength*capSign + ex*sign(dot(direction*capSign, (perpendicular(averageDirection))*edgeSign)); } + /* Cap is there only if neighbors are NaN, otherwise a joint is rendered */ + // TODO uhhhhh document why the sign comparison + if(all(isnan(neighborDirection)) || sign(centerDistanceSigned.x) != sign(halfSegmentLength*capSign)) + hasCap = abs(centerDistanceSigned.x); + else + hasCap = -abs(centerDistanceSigned.x); + gl_Position.xyzw = vec4(transformedPosition + edgeDirection, 0.0, 1.0); #elif defined(THREE_DIMENSIONS) // TODO 3D, how to handle perspective? multiply edgeDistance with w? diff --git a/src/Magnum/Shaders/Test/LineGLTest.cpp b/src/Magnum/Shaders/Test/LineGLTest.cpp index 13db421d0..da6d39fa1 100644 --- a/src/Magnum/Shaders/Test/LineGLTest.cpp +++ b/src/Magnum/Shaders/Test/LineGLTest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include "Magnum/GL/Renderbuffer.h" #include "Magnum/GL/RenderbufferFormat.h" #include "Magnum/Math/Color.h" +#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" #include "Magnum/Shaders/Generic.h" @@ -122,6 +124,7 @@ struct LineGLTest: GL::OpenGLTester { GL::Framebuffer _framebuffer{NoCreate}; }; +using namespace Containers::Literals; using namespace Math::Literals; const struct { @@ -180,27 +183,153 @@ const struct { Containers::Array lineSegments; Float width; Float smoothness; + LineGL2D::CapStyle capStyle; + bool reverse; + Matrix3 transform; + bool expectOverlap; const char* expected; } Render2DData[]{ - // TODO cap variants, short & long + {"line caps default, flat", {InPlaceInit, { + {-0.8f, 0.8f}, {-0.8f, 0.8f}, + {-0.8f, 0.4f}, {-0.4f, 0.4f}, + {-0.8f, 0.0f}, { 0.0f, 0.0f}, + {-0.8f, -0.4f}, { 0.4f, -0.4f}, + {-0.8f, -0.8f}, { 0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, false, {}, + false, "line-caps-square-flat.tga"}, + {"line caps square", {InPlaceInit, { + {-0.8f, 0.8f}, {-0.8f, 0.8f}, + {-0.8f, 0.4f}, {-0.4f, 0.4f}, + {-0.8f, 0.0f}, { 0.0f, 0.0f}, + {-0.8f, -0.4f}, { 0.4f, -0.4f}, + {-0.8f, -0.8f}, { 0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Square, false, {}, + false, "line-caps-square.tga"}, + {"line caps round", {InPlaceInit, { + {-0.8f, 0.8f}, {-0.8f, 0.8f}, + {-0.8f, 0.4f}, {-0.4f, 0.4f}, + {-0.8f, 0.0f}, { 0.0f, 0.0f}, + {-0.8f, -0.4f}, { 0.4f, -0.4f}, + {-0.8f, -0.8f}, { 0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Round, false, {}, + false, "line-caps-round.tga"}, + {"line caps triangle", {InPlaceInit, { + {-0.8f, 0.8f}, {-0.8f, 0.8f}, + {-0.8f, 0.4f}, {-0.4f, 0.4f}, + {-0.8f, 0.0f}, { 0.0f, 0.0f}, + {-0.8f, -0.4f}, { 0.4f, -0.4f}, + {-0.8f, -0.8f}, { 0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Triangle, false, {}, + false, "line-caps-triangle.tga"}, {"joint angles, obtuse", {InPlaceInit, { - { 0.2f, 0.8f}, {0.2f, 0.4f}, {0.2f, 0.4f}, {0.8f, 0.4f}, - {-0.4f, 0.4f}, {0.0f, 0.0f}, {0.0f, 0.0f}, {0.8f, 0.0f}, - {-0.8f, -0.0f}, {0.0f, -0.4f}, {0.0f, -0.4f}, {0.8f, -0.4f}, - {-0.8f, -0.8f}, {0.0f, -0.8f}, {0.0f, -0.8f}, {0.8f, -0.8f}, - }}, 10.0f, 0.0f, "joint-angles-obtuse.tga"}, - // TODO cap variants here also + { 0.4f, 0.8f}, {0.4f, 0.4f}, {0.4f, 0.4f}, {0.8f, 0.4f}, + {-0.2f, 0.4f}, {0.2f, 0.0f}, {0.2f, 0.0f}, {0.8f, 0.0f}, + {-0.8f, -0.0f}, {0.2f, -0.4f}, {0.2f, -0.4f}, {0.8f, -0.4f}, + {-0.8f, -0.8f}, {0.2f, -0.8f}, {0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, false, {}, + false, "joint-angles-obtuse.tga"}, + {"joint angles, obtuse, reverse direction", {InPlaceInit, { + /* Same as "joint angles, obtuse" */ + { 0.4f, 0.8f}, {0.4f, 0.4f}, {0.4f, 0.4f}, {0.8f, 0.4f}, + {-0.2f, 0.4f}, {0.2f, 0.0f}, {0.2f, 0.0f}, {0.8f, 0.0f}, + {-0.8f, -0.0f}, {0.2f, -0.4f}, {0.2f, -0.4f}, {0.8f, -0.4f}, + {-0.8f, -0.8f}, {0.2f, -0.8f}, {0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, true, {}, + false, "joint-angles-obtuse.tga"}, + {"joint angles, obtuse, transformed", {InPlaceInit, { + /* Same as "joint angles, obtuse" */ + { 0.4f, 0.8f}, {0.4f, 0.4f}, {0.4f, 0.4f}, {0.8f, 0.4f}, + {-0.2f, 0.4f}, {0.2f, 0.0f}, {0.2f, 0.0f}, {0.8f, 0.0f}, + {-0.8f, -0.0f}, {0.2f, -0.4f}, {0.2f, -0.4f}, {0.8f, -0.4f}, + {-0.8f, -0.8f}, {0.2f, -0.8f}, {0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, true, + Matrix3::scaling({100.0f, 2.0f})*Matrix3::rotation(45.0_degf), + false, "joint-angles-obtuse.tga"}, + {"joint angles, obtuse, round caps", {InPlaceInit, { + /* Same as "joint angles, obtuse" */ + { 0.4f, 0.8f}, {0.4f, 0.4f}, {0.4f, 0.4f}, {0.8f, 0.4f}, + {-0.2f, 0.4f}, {0.2f, 0.0f}, {0.2f, 0.0f}, {0.8f, 0.0f}, + {-0.8f, -0.0f}, {0.2f, -0.4f}, {0.2f, -0.4f}, {0.8f, -0.4f}, + {-0.8f, -0.8f}, {0.2f, -0.8f}, {0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Round, false, {}, + false, "joint-angles-obtuse-round-caps.tga"}, + {"joint angles, obtuse, round caps, reverse direction", {InPlaceInit, { + /* Same as "joint angles, obtuse" */ + { 0.4f, 0.8f}, {0.4f, 0.4f}, {0.4f, 0.4f}, {0.8f, 0.4f}, + {-0.2f, 0.4f}, {0.2f, 0.0f}, {0.2f, 0.0f}, {0.8f, 0.0f}, + {-0.8f, -0.0f}, {0.2f, -0.4f}, {0.2f, -0.4f}, {0.8f, -0.4f}, + {-0.8f, -0.8f}, {0.2f, -0.8f}, {0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Round, true, {}, + false, "joint-angles-obtuse-round-caps.tga"}, {"joint angles, acute", {InPlaceInit, { - { 0.4f, 0.8f}, {0.0f, 0.4f}, {0.0f, 0.4f}, {0.8f, 0.4f}, - { 0.8f, 0.0f}, {0.0f, -0.4f}, {0.0f, -0.4f}, {0.8f, -0.4f}, - { 0.8f, -0.8f}, {0.0f, -0.8f}, {0.0f, -0.8f}, {0.8f, -0.8f}, - }}, 10.0f, 0.0f, "joint-angles-acute.tga"}, - // TODO cap variants here also - {"joint angles, acute, short", {InPlaceInit, { - { -0.25f, 0.45f}, {-0.3f, 0.4f}, {-0.3f, 0.4f}, {0.6f, 0.4f}, - { -0.25f, -0.45f}, {-0.3f, -0.4f}, {-0.3f, -0.4f}, {0.6f, -0.4f}, - }}, 20.0f, 0.0f, "joint-angles-acute-short.tga"}, - // TODO cap variants here also + { 0.1f, 0.8f}, {-0.2f, 0.5f}, {-0.2f, 0.5f}, {0.8f, 0.5f}, + { 0.6f, 0.2f}, {-0.2f, -0.2f}, {-0.2f, -0.2f}, {0.8f, -0.2f}, + { 0.6f, -0.5f}, {-0.2f, -0.8f}, {-0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, false, {}, + false, "joint-angles-acute.tga"}, + // TODO joint styles + {"joint angles, acute, reverse direction", {InPlaceInit, { + /* Same as "joint angles, acute" */ + { 0.1f, 0.8f}, {-0.2f, 0.5f}, {-0.2f, 0.5f}, {0.8f, 0.5f}, + { 0.6f, 0.2f}, {-0.2f, -0.2f}, {-0.2f, -0.2f}, {0.8f, -0.2f}, + { 0.6f, -0.5f}, {-0.2f, -0.8f}, {-0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, true, {}, + false, "joint-angles-acute.tga"}, + {"joint angles, acute, transformed", {InPlaceInit, { + /* Same as "joint angles, transformed" */ + { 0.1f, 0.8f}, {-0.2f, 0.5f}, {-0.2f, 0.5f}, {0.8f, 0.5f}, + { 0.6f, 0.2f}, {-0.2f, -0.2f}, {-0.2f, -0.2f}, {0.8f, -0.2f}, + { 0.6f, -0.5f}, {-0.2f, -0.8f}, {-0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, false, + Matrix3::scaling({100.0f, 2.0f})*Matrix3::rotation(45.0_degf), + false, "joint-angles-acute.tga"}, + {"joint angles, acute, round caps", {InPlaceInit, { + /* Same as "joint angles, acute" */ + { 0.1f, 0.8f}, {-0.2f, 0.5f}, {-0.2f, 0.5f}, {0.8f, 0.5f}, + { 0.6f, 0.2f}, {-0.2f, -0.2f}, {-0.2f, -0.2f}, {0.8f, -0.2f}, + { 0.6f, -0.5f}, {-0.2f, -0.8f}, {-0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Round, false, {}, + false, "joint-angles-acute-round-caps.tga"}, + {"joint angles, acute, round caps, reverse direction", {InPlaceInit, { + /* Same as "joint angles, acute" */ + { 0.1f, 0.8f}, {-0.2f, 0.5f}, {-0.2f, 0.5f}, {0.8f, 0.5f}, + { 0.6f, 0.2f}, {-0.2f, -0.2f}, {-0.2f, -0.2f}, {0.8f, -0.2f}, + { 0.6f, -0.5f}, {-0.2f, -0.8f}, {-0.2f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Round, true, {}, + false, "joint-angles-acute-round-caps.tga"}, + {"joint angles, short cap", {InPlaceInit, { + {-0.3f, 0.6f}, {-0.6f, 0.6f}, {-0.6f, 0.6f}, {-0.6f, -0.6f}, {-0.6f, -0.6f}, {-0.4f, -0.6f}, + { 0.6f, 0.6f}, {0.5f, 0.6f}, {0.5f, 0.6f}, {0.5f, -0.6f}, {0.5f, -0.6f}, {0.51f, -0.6f}, + }}, 20.0f, 1.0f, LineGL2D::CapStyle::Round, false, {}, + true, "joint-angles-short-cap.tga"}, + {"joint angles, short cap, reversed", {InPlaceInit, { + {-0.3f, 0.6f}, {-0.6f, 0.6f}, {-0.6f, 0.6f}, {-0.6f, -0.6f}, {-0.6f, -0.6f}, {-0.4f, -0.6f}, + { 0.6f, 0.6f}, {0.5f, 0.6f}, {0.5f, 0.6f}, {0.5f, -0.6f}, {0.5f, -0.6f}, {0.51f, -0.6f}, + }}, 20.0f, 1.0f, LineGL2D::CapStyle::Round, true, {}, // TODO no difference + true, "joint-angles-short-cap.tga"}, + {"joint angles, short cap, acute", {InPlaceInit, { + { 0.6f, 0.8f}, {0.0f, 0.6f}, {0.0f, 0.6f}, {0.8f, 0.6f}, + { 0.6f, 0.2f}, {0.0f, 0.05f}, {0.0f, 0.05f}, {0.8f, 0.05f}, + { 0.6f, -0.35f}, {0.0f, -0.45f}, {0.0f, -0.45f}, {0.8f, -0.45f}, + { 0.6f, -0.8f}, {0.0f, -0.8f}, {0.0f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, false, {}, + true, "joint-angles-short-cap-acute.tga"}, + {"joint angles, short cap, acute reverse direction", {InPlaceInit, { + /* Same as "joint angles, short cap, acute" */ + { 0.6f, 0.8f}, {0.0f, 0.6f}, {0.0f, 0.6f}, {0.8f, 0.6f}, + { 0.6f, 0.2f}, {0.0f, 0.05f}, {0.0f, 0.05f}, {0.8f, 0.05f}, + { 0.6f, -0.35f}, {0.0f, -0.45f}, {0.0f, -0.45f}, {0.8f, -0.45f}, + { 0.6f, -0.8f}, {0.0f, -0.8f}, {0.0f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 0.0f, {}, true, {}, + true, "joint-angles-short-cap-acute.tga"}, + {"joint angles, short cap, acute, round", {InPlaceInit, { + /* Same as "joint angles, short cap, acute" */ + { 0.6f, 0.8f}, {0.0f, 0.6f}, {0.0f, 0.6f}, {0.8f, 0.6f}, + { 0.6f, 0.2f}, {0.0f, 0.05f}, {0.0f, 0.05f}, {0.8f, 0.05f}, + { 0.6f, -0.35f}, {0.0f, -0.45f}, {0.0f, -0.45f}, {0.8f, -0.45f}, + { 0.6f, -0.8f}, {0.0f, -0.8f}, {0.0f, -0.8f}, {0.8f, -0.8f}, + }}, 10.0f, 1.0f, LineGL2D::CapStyle::Round, false, {}, + true, "joint-angles-short-cap-acute-round.tga"}, }; LineGLTest::LineGLTest() { @@ -655,7 +784,7 @@ void LineGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -template GL::Mesh generateLineMesh(Containers::ArrayView> lineSegments) { +template GL::Mesh generateLineMesh(Containers::StridedArrayView1D> lineSegments) { struct Vertex { VectorTypeFor previousPosition; VectorTypeFor position; @@ -663,7 +792,8 @@ template GL::Mesh generateLineMesh(Containers::ArrayView }; CORRADE_INTERNAL_ASSERT(lineSegments.size() % 2 == 0); - Containers::Array vertices{NoInit, lineSegments.size()*2}; + /* Not NoInit, because we're subsequently checking for NaNs */ + Containers::Array vertices{ValueInit, lineSegments.size()*2}; for(std::size_t i = 0; i != lineSegments.size(); ++i) vertices[i*2 + 0].position = vertices[i*2 + 1].position = lineSegments[i]; @@ -766,6 +896,13 @@ template void LineGLTest::renderDefaults2D() { .setFlags(flag)}; shader.setViewportSize(Vector2{RenderSize}); + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + if(flag == LineGL2D::Flag{}) { shader.draw(lines); } @@ -789,6 +926,8 @@ template void LineGLTest::renderDefaults2D() { #endif else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + GL::Renderer::disable(GL::Renderer::Feature::Blending); + MAGNUM_VERIFY_NO_GL_ERROR(); if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || @@ -817,19 +956,30 @@ template void LineGLTest::render2D() { } #endif - GL::Mesh lines = generateLineMesh<2>(data.lineSegments); + Containers::Array transformedLineSegments{NoInit, data.lineSegments.size()}; + Utility::copy(data.lineSegments, transformedLineSegments); + for(Vector2& i: transformedLineSegments) + i = data.transform.transformPoint(i); + + GL::Mesh lines = generateLineMesh<2>( + data.reverse ? stridedArrayView(transformedLineSegments).flipped<0>() : transformedLineSegments); LineGL2D shader{LineGL2D::Configuration{} - .setFlags(flag)}; + .setFlags(flag) + .setCapStyle(data.capStyle)}; shader.setViewportSize(Vector2{RenderSize}); shader .setWidth(data.width) .setSmoothness(data.smoothness) - // .setBackgroundColor(0xff0000ff_rgbaf) - // .setColor(0x557766_rgbf) - ; + .setTransformationProjectionMatrix(data.transform.inverted()) + .setColor(0x80808080_rgbaf); - // TODO test with blending -- there should be no self-intersection + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); if(flag == LineGL2D::Flag{}) { shader.draw(lines); @@ -854,17 +1004,27 @@ template void LineGLTest::render2D() { #endif else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + GL::Renderer::disable(GL::Renderer::Feature::Blending); + 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."); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}); + CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ - Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Containers::arrayCast(image.pixels()), Utility::Path::join({SHADERS_TEST_DIR, "LineTestFiles", data.expected}), (DebugTools::CompareImageToFile{_manager})); + + { + CORRADE_EXPECT_FAIL_IF(data.expectOverlap, "Rendered with overlapping geometry at the moment."); + CORRADE_COMPARE(Math::max(image.pixels().asContiguous()), 0x888888ff_rgba); + } + } }}}} diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute-overlapping-cap.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute-overlapping-cap.tga new file mode 100644 index 0000000000000000000000000000000000000000..173e6063b6df0d6ed5b2a99b8770dbdad821b3f5 GIT binary patch literal 1008 zcmd6lNlpS$6h(`vGiEM}ErIln5I}`!0ErqzViyHsFJWX%+>+;(_lF8AkmTl_I#m7o z@BNCRohbY=Q6}1re$wf5$QV9duh*q=xtxRl+jZDysNddasC!%&*#F8tMGs(&l+bhQ zBDx7zaZ|fGt^?O`1G_fv2;RrF?RvOlxQPp68sPLyG2zZ|LqC&uo#Og%m3m9`KJlE? z;TrXY6wNC*1ygie=sH>5;TL!5!1AO3r`QSQ-C8SDHhN*Lo*Nr|W8Ffx;4F(w?7FzF z-N;U(=-H`i11E}|){q;Kcoct|ik-$2*3qfyz6sH2)$5FC^xLC)z{f{NU6LyIY>({& zpR-TSDzMi4y(0P) m#Mk5i*1qnDJ~eT&*9F)5jMeKC0b_oJb>Q;qI}7VrL$AN5HEAjU literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute-round-caps.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute-round-caps.tga new file mode 100644 index 0000000000000000000000000000000000000000..ba103de2d9832c5f9fad474dc1ce9406d8a0bd1a GIT binary patch literal 3062 zcma);IZqo=6h^%fk#ry-843##ORi~MQSbvy>(&L8JLHPSU}8f6vjh@C$i65fgaAnh zAe1IOe<|miAv1o<2#<6NnDg$v?=GJwm3ol+|5Q?yR6cbxG&Cg33=a>FkB=9N#lKi) zVqzkn&-4Elwt(eEMn+1d()9FntyXI^8h^0d+}s?0O-)UOthgm?V03hJa&od#snqNB z#l^)=r*n;Eo6Y9T%uL8O;o8`}FmAbAu2!q~Y`5E>S6Bud*DzvNuz}tj=I7^uF6EaT zLZ>im;@tS>;z*Z!0q}G&f9xMNx&`V9eQJl^5$A^f24~*oX=3a{`LxUQ+#i1zHcb^M z+FC+04*VJIa56`sIYrSaJss~jU4NCBrOpq~SGr4&=`@!HUd7%jqT43rnj_cVX;DW% z2zN`S07e;>;XYyhj%|a-6&8Cazit{^0;6f^038W*6X*(z#zo*U=8wMWu%~kBMqhEF z|MW+HBruwO-RC?p`U0#|s!QZm)u4D1laUI&OUE@pkSFP zQKS!AW;@(>JDe8ThK`)f_dJw$(f2*z2JX@nk0Ng;6k1zuXQ5+Rj?p3-MH9PDBembY zW4Y)U&4YP@z3G#cRKDwz-GI~n_mjgqoc6yTRulaBPBxr&Y0#Gh*4Zj)g`Gek6vnDb zC~Rt7GZD?HCeA+HWBeHp7c%)J*qdjIe^dcJ@q{lIjgo22|X; UXR2VJb}+%3-R_y{b#rU~04W4noB#j- literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-acute.tga new file mode 100644 index 0000000000000000000000000000000000000000..5a29e1f7042fc4672f51ea43b99a15d1b4ecbdf9 GIT binary patch literal 930 zcmZ{i%TB^j5J0(ujiwhn^z>D-w=Vl`xQK9jBW9oZgwm+gH< l)Pg+&XUdujLNC7SdFaKSSA_HBKAz1g>^{!&3hX}4+8^5WAB+G1 literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-obtuse-round-caps.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-obtuse-round-caps.tga new file mode 100644 index 0000000000000000000000000000000000000000..62003727c6c11906f629139b1e25b476db1a08a6 GIT binary patch literal 3524 zcmeH~&u&st5XRvGiMk_f2+OA733vb!UZ73-2J{IC>9PqGDYdmq3vI2nf1)e`~q5Ts8NnTrNwVAyKtjU0GRK zU0uCLA|6>_y^lOaio7<=#~I#G#VQl8=X#PFc^@~ zbLHEm?1OaExZB&?=v^W3lV_9Dw7bU+H!KoCSvJhESAyDi}s_Nf6X0p8G5CJ(aR zmD2903N&w!=d$pNL;(hTi2Yqz_@y$n@O|8(tqxe;hqPz)f99nm)p#7=6lsQM#Tjuj zMibSzuzZ2!8Wp|%aM}kr$i~Ypf1p}i_@Nvqc9o!E{?j@YQiZ@ zJV_73;krfh;85u_%-Cxh z&0^1>U<}M8xIp9TL9MrFXv!?cl7=)FYss6uF4hu9{#?{uu-Hy)_BuXFjmZPZU7bNw)L)St$CM(9=0ZX#Dk@p(DJ?KTc;zp)23-^S!+NhzWubNs576K z>f9MOrk*t#6NDgjDxyc6z-d@sO_%8^O(1l_{ftUdvAaF0X=q#Lv4)L3B#K*ECHB=* zjf^Vmnuh=P-Bbqh6SskgisCwt^JkT66xX8mzadEvs~D3e9nJQw|9Mi?W=cg`-;qV- Qbj1D-#4QwE3iB`jzxY+pb^rhX literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-obtuse.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-obtuse.tga index a63a9773986d01ac382c4e637a41e81fc7f9c562..cdb5db8e18800ef0678a2c4709b2976aab273668 100644 GIT binary patch literal 1066 zcmdUuOHKko6h&+7tYHrMdhyoaC7Tevo_;5YLEKAIok z5AzH@nWyllc>>SjC0Us_>xCPHit@}i- z&FZasEw6s*5zFbFSYtIutUf-PFG>6a}%yIclsxse}6w3*iPjD literal 1066 zcmd6kSxN&z6h-UXzy8azvZc(%jFlKiM1o{CMn%Le_?0EK=fZsuT!7fn+{5YWu2;8H zI!kd?Q#Do6u_%hT>+pA3mdd~6*#CDfm}BXAp5V244sXqm@U!^=elg#}ujX6$-Fyvy zm}l^(c?y4-GdzP=R->*U%1#${e{b3r~blee!Z*yPTc^f{=#Lr yhf{yyw1;|8f8jKXdQpGjG;<55{=(@@4V?DZH||KCxd!JGr!&X%|IA0`jQa;En$64r diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap-acute-round.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap-acute-round.tga new file mode 100644 index 0000000000000000000000000000000000000000..77e5c476a820bd8030b06206a4adddef343f5784 GIT binary patch literal 3063 zcmb7GO-~w86h*+c+IC^wz!nJ$62gMGF)?xHh6Qfj;=&&QlCU6rjEF6^#j3SQsn`~Z zH5EwIwxlK`H8EZFFZCRT8Rp~h94EPfWX?J7zI*R^Z(Ocfm-ZxF3760HZ*Fdmaf+xf zE-t!bu~;}94hDk?!W{?%d_EuEDH^8R?Rq<%PP5sp)oR&nc57>^i14OTsbn&_va+J$ zizEEW?d`2+Fc`Gk?P|4JsZ>e`PoYp)TU%3+g%Pp=q#MXaqXCG5a03QGyw@p{R;~V(P(sed08mH ze!hml83l|@D?{L^6NMcx_n}a?Dpbb`D2OD7mtY=1fx~0k#J)@+olfgzcm?Jm6vlJP zkvGrsu~RXo!J4}Di7qlRYnwhg)jI&Y1g$5qa6X^MDBuxT_lr}(eeO+fHhnrZJ}|yH zRbkb~71{-@>y$b?1dR0{`x^0F9T;J|EjxYz_;19tO2J`)N8JVoNAr^QYxk6AX=y2u zNVHlll=BZlbR4ockD(e0g;e;)#zrQSQ4|FYE+B41t=*o;0-D261oPTdKQ6>7h!+EN z8&LwSgJ08-@5xvIR}`?ml4Z1KO~zttx4}BGvJPb|ny3ucJF@ig0^_}m#bG3i?D06U z9CaD(a73HJ?SsFziK4)%OyC%-8Mq3UqIx5WP1TfXFbTI_)e4|zGB^(gGyJ;@zDKxT zN2>Q^aG(tZ^Y1pWs9;It?Kmi>_hEKM}U0m6i}-?T&jR23}HBuRJhs#kd}EC=QAM0G=a+ zVBrfZj*bxE&aLZy5aZ}(vsX^Uz|Ryk9<;Q@Coz_WpeR1_(1J literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap-acute.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap-acute.tga new file mode 100644 index 0000000000000000000000000000000000000000..173e6063b6df0d6ed5b2a99b8770dbdad821b3f5 GIT binary patch literal 1008 zcmd6lNlpS$6h(`vGiEM}ErIln5I}`!0ErqzViyHsFJWX%+>+;(_lF8AkmTl_I#m7o z@BNCRohbY=Q6}1re$wf5$QV9duh*q=xtxRl+jZDysNddasC!%&*#F8tMGs(&l+bhQ zBDx7zaZ|fGt^?O`1G_fv2;RrF?RvOlxQPp68sPLyG2zZ|LqC&uo#Og%m3m9`KJlE? z;TrXY6wNC*1ygie=sH>5;TL!5!1AO3r`QSQ-C8SDHhN*Lo*Nr|W8Ffx;4F(w?7FzF z-N;U(=-H`i11E}|){q;Kcoct|ik-$2*3qfyz6sH2)$5FC^xLC)z{f{NU6LyIY>({& zpR-TSDzMi4y(0P) m#Mk5i*1qnDJ~eT&*9F)5jMeKC0b_oJb>Q;qI}7VrL$AN5HEAjU literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap.tga b/src/Magnum/Shaders/Test/LineTestFiles/joint-angles-short-cap.tga new file mode 100644 index 0000000000000000000000000000000000000000..0a197dca68f23dd03393d178a027a4b50f333f86 GIT binary patch literal 4728 zcmeI0J#XSb5QaJ11nD{oBpNEHNKKV4B~?V~NcjVlNtFf&pHBEn4sst2MN*_75ke>l zGzj|pOZN_ocNuK2p}PX4De%rbo?Xw5-*qVTJ>>k(LT90P=x;O{-L_j?>!wmEGnq^# z5{XzWh6|4{Ba_J#3WZLmQ-c|;R?D{STCH{qGp?_%5d*I(j1b@O7Z=;G@YU5-KA*q1 zxHvsM1r?)Rm;sOX_xG=_uNo};@$oSljb2_}`u%>R(EzmI38pYWG1i4yl}ZI{faCEv zpzSpn9?Rv@hDBzx+3V};!@~pI@xutuQ~@6`Y+{P%=jWI?NG+Ht?+4f&EIb~MpPrud zs2_wvZa|5|nm}0+h0!(5noK6WUQd%%56%X%!W_XMj>?#p=pH2V?d|R6=EiSgeL`TF z#NyvGL=j^&EptAfx7%$k0uvHha71a1FrJ^EkB^Umcd9^TO6z5y1VlaEu@tC+=usIc zfRmFG|9DJ6JQyBdMtpHZJOkr>@~80X zF8TYcUmfukir1{)sswzVA8ocW#cQ6Qu7s!yln@_KLb^h}1ENR>eIa{pD^+CopoBpQ z+*6)AU2&S=Sb=*yp#Ol;hPhj#x(|(8Cox$f$p%;re%}aZt zQirwLuO`K-O z5)Ru!pDVZ)JWRaxyWMU&o%R)QOx)XWQ;SWn`~nFOP%bYoh0LtLg$^^y?I+!6Q`a}x zFbNQnF&3004oFh{mtCM=@KmavQ%DSvo+1j4T2lMFDZhy#g?*jnvO1fA5*8GAN=KR# zpVtZ`XFO{&eFH!N@~TDnwhf@xzfyxC4GHZQvY+7 zqu&`z`tHYa&JugEWZpb4=Ul^B($^<1xt^TmoUs^7`np-Vf^g1zI~9bkMCt5F$5#cy z?8FJpU0J#vL+9Y;PCLeP16xw}f&+|$(r;lp(z-ed`&`fhE1;nS^fo7%#bV*r-qv4P z9`#)oUsb8l!4fdW J_SYo({|{6$>9_y@ literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/line-caps-round.tga b/src/Magnum/Shaders/Test/LineTestFiles/line-caps-round.tga new file mode 100644 index 0000000000000000000000000000000000000000..c0889887a0249b6116b71a6288bcb8b9188a8011 GIT binary patch literal 2162 zcmb7_&uW7}5XQyiPbnRpW~^nQaNptwi)VHieH6vy!q;bV#o?9UN*h%uH)l4Mzy z=lOIxJtD@KVguW?cLpED2+K50i=rsY@`M;+ijCS;G%g?S)s{0XKBm}!Mmhy$gwvt@ z&=Hu{@3V{)aX-tLVgs6^eED#y;972O6%OfHU;mjCkJ`UeyjIBGcK6Q0YYDGW&7PY5 zpVw34YI|~krR7Ds=JiRs@OorjfK|ExbJhi!dA??5=I2)BkiPXXU{*Y8|5`EIqC>X3 z*A`|Qc#Uc{U1qP1t7W!T%t+VF-W0PJ)`eN63o~b3n3?BmW@dhFRSxM}9|LB^qxLTp zvjaM0yL(|_wr^;t@8Yt{?4xnD%vOpS>6+QIV)o9uFspQ7=Bx`d^L)+B%+FwEt#U}; z`WP@P9?4JtDl`EPCRm>lbmtA0@()8_@QIRt*9p3j{jAIAyK%LQ=86&Nn$d$|^u;=I dLjuq$T^Kp*!pJ;dGcxmYtJ0-kmwL@C{s6J%HIV=S literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/line-caps-square-flat.tga b/src/Magnum/Shaders/Test/LineTestFiles/line-caps-square-flat.tga new file mode 100644 index 0000000000000000000000000000000000000000..b34e9f723fdca284c045ecd5ac0bfce5368b17ac GIT binary patch literal 738 zcmZQz;9`J*0EPet35N56f`TyI1Z1D==;#2^^kl>AN2ft<*a>vcB&d7n#YT4rj1O|p vN}zidLES?yHq3r>8swgtK=*8dx`$qDba%k`Aop|v-E#=)9(u81_Je2uJR;ns literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/line-caps-square.tga b/src/Magnum/Shaders/Test/LineTestFiles/line-caps-square.tga new file mode 100644 index 0000000000000000000000000000000000000000..5b87a4aab22a20f6734dccf3d3f374e4fbfab269 GIT binary patch literal 1748 zcmd_mF$w}P5Czb2?d;NdfE;K~lio%GHrj|_qm2kQ+K6DIjR+QA$v-gP5RyGX0-u?k z4_iEuiSYH(%T&J3In50Dc<*&;M%Y6LZ=}PD3!WDGg=38Ig^aPbQ4fMpjgp*-b%)K$ z;OqxCi0O*!(wani1!?yuZKWTUp&v~-)%FC$eQtY9w!6aiklD7DD0PbV%|7BI!mXD-h}G zn&df4d^O*J@7hnQeWZ?b+dn&ajds|kf4F~{5M%#hZE+@!y3F?jA}M+#8xZNFPx755 NzMAjA^Q^fv?;l*ukS+iK literal 0 HcmV?d00001