From 0f7598220dab1094747d19cb7497e75dddc28b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 22 May 2025 19:36:31 +0200 Subject: [PATCH] Primitives: support texture coordinates and tangents in cubeSolid(). Well, guess why I was postponing this for so long, because there are way too many combinations, then there's too many ways each variant can go wrong, one has to document and test everything REAL GOOd and even then it likely doesn't contain all variants people may want. --- doc/artwork/primitives-cube.svg | 1315 +++++++++++++++++ doc/snippets/README.md | 4 + doc/snippets/primitives-cube-all-same.svg | 45 + ...ves-cube-negative-x-up-negative-x-down.svg | 50 + ...ves-cube-negative-x-up-negative-z-down.svg | 52 + ...ves-cube-negative-x-up-positive-x-down.svg | 45 + ...ves-cube-negative-x-up-positive-z-down.svg | 47 + ...mitives-cube-positive-up-negative-down.svg | 50 + ...ves-cube-positive-z-up-positive-x-down.svg | 42 + ...ves-cube-positive-z-up-positive-z-down.svg | 45 + src/Magnum/Primitives/CMakeLists.txt | 2 +- src/Magnum/Primitives/Cube.cpp | 511 ++++++- src/Magnum/Primitives/Cube.h | 119 +- src/Magnum/Primitives/Test/CMakeLists.txt | 2 +- src/Magnum/Primitives/Test/CubeTest.cpp | 506 ++++++- 15 files changed, 2815 insertions(+), 20 deletions(-) create mode 100644 doc/artwork/primitives-cube.svg create mode 100644 doc/snippets/primitives-cube-all-same.svg create mode 100644 doc/snippets/primitives-cube-negative-x-up-negative-x-down.svg create mode 100644 doc/snippets/primitives-cube-negative-x-up-negative-z-down.svg create mode 100644 doc/snippets/primitives-cube-negative-x-up-positive-x-down.svg create mode 100644 doc/snippets/primitives-cube-negative-x-up-positive-z-down.svg create mode 100644 doc/snippets/primitives-cube-positive-up-negative-down.svg create mode 100644 doc/snippets/primitives-cube-positive-z-up-positive-x-down.svg create mode 100644 doc/snippets/primitives-cube-positive-z-up-positive-z-down.svg diff --git a/doc/artwork/primitives-cube.svg b/doc/artwork/primitives-cube.svg new file mode 100644 index 000000000..553e8dfd6 --- /dev/null +++ b/doc/artwork/primitives-cube.svg @@ -0,0 +1,1315 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + + + + + + + + + + diff --git a/doc/snippets/README.md b/doc/snippets/README.md index c886790cd..f5be29ad3 100644 --- a/doc/snippets/README.md +++ b/doc/snippets/README.md @@ -36,3 +36,7 @@ The `doc/artwork/line-quad-data-other.svg` is derived from making the canvas smaller, the `doc/artwork/line-quad-data.svg` is then derived from `doc/artwork/line-quad-data-other.svg` by removing the "other" layer and making the canvas smaller yet again. + +### primitives-cube-*.svg + +Like above, created from `doc/artwork/primitives-cube.svg`. diff --git a/doc/snippets/primitives-cube-all-same.svg b/doc/snippets/primitives-cube-all-same.svg new file mode 100644 index 000000000..2a5c98ecb --- /dev/null +++ b/doc/snippets/primitives-cube-all-same.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + −Y + −Z + + +Z + +X + +Y + + + + + diff --git a/doc/snippets/primitives-cube-negative-x-up-negative-x-down.svg b/doc/snippets/primitives-cube-negative-x-up-negative-x-down.svg new file mode 100644 index 000000000..f6a3d9b65 --- /dev/null +++ b/doc/snippets/primitives-cube-negative-x-up-negative-x-down.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + + + + + + + + diff --git a/doc/snippets/primitives-cube-negative-x-up-negative-z-down.svg b/doc/snippets/primitives-cube-negative-x-up-negative-z-down.svg new file mode 100644 index 000000000..b30bf5a90 --- /dev/null +++ b/doc/snippets/primitives-cube-negative-x-up-negative-z-down.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/primitives-cube-negative-x-up-positive-x-down.svg b/doc/snippets/primitives-cube-negative-x-up-positive-x-down.svg new file mode 100644 index 000000000..51290021d --- /dev/null +++ b/doc/snippets/primitives-cube-negative-x-up-positive-x-down.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + + + + diff --git a/doc/snippets/primitives-cube-negative-x-up-positive-z-down.svg b/doc/snippets/primitives-cube-negative-x-up-positive-z-down.svg new file mode 100644 index 000000000..a6137b772 --- /dev/null +++ b/doc/snippets/primitives-cube-negative-x-up-positive-z-down.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + diff --git a/doc/snippets/primitives-cube-positive-up-negative-down.svg b/doc/snippets/primitives-cube-positive-up-negative-down.svg new file mode 100644 index 000000000..79f5fc966 --- /dev/null +++ b/doc/snippets/primitives-cube-positive-up-negative-down.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + −Y + −Z + + +Z + +X + +Y + + + + + + + + + + diff --git a/doc/snippets/primitives-cube-positive-z-up-positive-x-down.svg b/doc/snippets/primitives-cube-positive-z-up-positive-x-down.svg new file mode 100644 index 000000000..f7ea96add --- /dev/null +++ b/doc/snippets/primitives-cube-positive-z-up-positive-x-down.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + diff --git a/doc/snippets/primitives-cube-positive-z-up-positive-z-down.svg b/doc/snippets/primitives-cube-positive-z-up-positive-z-down.svg new file mode 100644 index 000000000..8052f0068 --- /dev/null +++ b/doc/snippets/primitives-cube-positive-z-up-positive-z-down.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −X + +Z + +X + −Z + +Y + −Y + + + + + + + + diff --git a/src/Magnum/Primitives/CMakeLists.txt b/src/Magnum/Primitives/CMakeLists.txt index 31871b2bb..429946775 100644 --- a/src/Magnum/Primitives/CMakeLists.txt +++ b/src/Magnum/Primitives/CMakeLists.txt @@ -31,7 +31,6 @@ set(CMAKE_FOLDER "Magnum/Primitives") set(MagnumPrimitives_SRCS Axis.cpp Crosshair.cpp - Cube.cpp Gradient.cpp Grid.cpp Icosphere.cpp @@ -46,6 +45,7 @@ set(MagnumPrimitives_GracefulAssert_SRCS Capsule.cpp Circle.cpp Cone.cpp + Cube.cpp Cylinder.cpp UVSphere.cpp) diff --git a/src/Magnum/Primitives/Cube.cpp b/src/Magnum/Primitives/Cube.cpp index 3704b699e..d68031124 100644 --- a/src/Magnum/Primitives/Cube.cpp +++ b/src/Magnum/Primitives/Cube.cpp @@ -36,6 +36,10 @@ namespace { /* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ constexpr UnsignedShort IndicesSolid[]{ + /* 3--2 + | /| + |/ | + 0--1 */ 0, 1, 2, 0, 2, 3, /* +Z */ 4, 5, 6, 4, 6, 7, /* +X */ 8, 9, 10, 8, 10, 11, /* +Y */ @@ -47,34 +51,44 @@ constexpr struct VertexSolid { Vector3 position; Vector3 normal; } VerticesSolid[]{ + /* 11----10 23 14----15 + / +Y / 6 /| | | + 8------9 / | 22 | | -Z | + 3------2 7 | |-X| | | + | | |+X| | 20 13----12 + | +Z | | 5 | / 16----17 + | | | / 21 / -Y / + 0------1 4 19----18 */ + + /* 0, +Z */ {{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, {{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, - {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, /* +Z */ + {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, {{-1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, - + /* 4, +X */ {{ 1.0f, -1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, {{ 1.0f, -1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, - {{ 1.0f, 1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, /* +X */ + {{ 1.0f, 1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, {{ 1.0f, 1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, - + /* 8, +Y */ {{-1.0f, 1.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}, {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}, - {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, /* +Y */ + {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, {{-1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, - + /* 12, -Z */ {{ 1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, {{-1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, - {{-1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, /* -Z */ + {{-1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, - + /* 16, -Y */ {{-1.0f, -1.0f, -1.0f}, { 0.0f, -1.0f, 0.0f}}, {{ 1.0f, -1.0f, -1.0f}, { 0.0f, -1.0f, 0.0f}}, - {{ 1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, /* -Y */ + {{ 1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, {{-1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, - + /* 20, -X */ {{-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, {{-1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, - {{-1.0f, 1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, /* -X */ + {{-1.0f, 1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, {{-1.0f, 1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}} }; constexpr Trade::MeshAttributeData AttributesSolid[]{ @@ -96,6 +110,481 @@ Trade::MeshData cubeSolid() { namespace { +/* GCC 4.8 dies with "error: array must be initialized with a brace-enclosed + initializer" if it's `constexpr Vector2 TextureCoordinates[][24]`. Turning + the second dimension into a struct seems to work around the problem. */ +/** @todo drop once GCC 4.8 is gone */ +constexpr struct { + Vector2 v[24]; +} TextureCoordinates[]{ + /* All same + 3--2 + | | + 0--1 */ + {{/* 0, +Z */ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, + {0.0f, 1.0f}, + /* 4, +X */ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, + {0.0f, 1.0f}, + /* 8, +Y */ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, + {0.0f, 1.0f}, + /* 12, -Z */ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, + {0.0f, 1.0f}, + /* 16, -Y */ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, + {0.0f, 1.0f}, + /* 20, -X */ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, + {0.0f, 1.0f}}}, + /* Positive up, negative down + +----+----3----2 1.0 + | +X | +Y | +Z | + +----+----0----1 0.5 + | -X | -Y | -Z | + +----+----+----+ 0.0 + 0.0 0.333 0.667 1.0 */ + {{/* 0, +Z */ + {2.0f/3.0f, 0.5f}, + {3.0f/3.0f, 0.5f}, + {3.0f/3.0f, 1.0f}, + {2.0f/3.0f, 1.0f}, + /* 4, +X */ + {0.0f/3.0f, 0.5f}, + {1.0f/3.0f, 0.5f}, + {1.0f/3.0f, 1.0f}, + {0.0f/3.0f, 1.0f}, + /* 8, +Y */ + {1.0f/3.0f, 0.5f}, + {2.0f/3.0f, 0.5f}, + {2.0f/3.0f, 1.0f}, + {1.0f/3.0f, 1.0f}, + /* 12, -Z */ + {2.0f/3.0f, 0.0f}, + {3.0f/3.0f, 0.0f}, + {3.0f/3.0f, 0.5f}, + {2.0f/3.0f, 0.5f}, + /* 16, -Y */ + {1.0f/3.0f, 0.0f}, + {2.0f/3.0f, 0.0f}, + {2.0f/3.0f, 0.5f}, + {1.0f/3.0f, 0.5f}, + /* 20, -X */ + {0.0f/3.0f, 0.0f}, + {1.0f/3.0f, 0.0f}, + {1.0f/3.0f, 0.5f}, + {0.0f/3.0f, 0.5f}}}, + /* -X up, -X down + +----+ 1.0 + | +Y | + A----C----E----G----+ 0.667 + | -X | +Z | +X | -Z | + B----D----F----H----+ 0.333 + | -Y | + +----+ 0.0 + 0.0 0.25 0.5 0.75 1.0 */ + {{/* 0, +Z */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.25f, 2.0f/3.0f}, /* C */ + /* 4, +X */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 2.0f/3.0f}, /* G */ + {0.50f, 2.0f/3.0f}, /* E */ + /* 8, +Y */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.25f, 3.0f/3.0f}, + {0.00f, 3.0f/3.0f}, + {0.00f, 2.0f/3.0f}, /* A */ + /* 12, -Z */ + {0.75f, 1.0f/3.0f}, /* H */ + {1.00f, 1.0f/3.0f}, + {1.00f, 2.0f/3.0f}, + {0.75f, 2.0f/3.0f}, /* G */ + /* 16, -Y */ + {0.00f, 1.0f/3.0f}, /* B */ + {0.00f, 0.0f/3.0f}, + {0.25f, 0.0f/3.0f}, + {0.25f, 1.0f/3.0f}, /* D */ + /* 20, -X */ + {0.00f, 1.0f/3.0f}, /* B */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.00f, 2.0f/3.0f}}},/* A */ + /* -X up, +Z down + +----+ + | +Y | + A----C----E----G----+ + | -X | +Z | +X | -Z | + +----D----F----H----+ + | -Y | + +----+ + 0.0 0.25 0.5 */ + {{/* 0, +Z */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.25f, 2.0f/3.0f}, /* C */ + /* 4, +X */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 2.0f/3.0f}, /* G */ + {0.50f, 2.0f/3.0f}, /* E */ + /* 8, +Y */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.25f, 3.0f/3.0f}, + {0.00f, 3.0f/3.0f}, + {0.00f, 2.0f/3.0f}, /* A */ + /* 12, -Z */ + {0.75f, 1.0f/3.0f}, /* H */ + {1.00f, 1.0f/3.0f}, + {1.00f, 2.0f/3.0f}, + {0.75f, 2.0f/3.0f}, /* G */ + /* 16, -Y */ + {0.25f, 0.0f/3.0f}, + {0.50f, 0.0f/3.0f}, + {0.50f, 1.0f/3.0f}, /* F */ + {0.25f, 1.0f/3.0f}, /* D */ + /* 20, -X */ + {0.00f, 1.0f/3.0f}, + {0.25f, 1.0f/3.0f}, /* D */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.00f, 2.0f/3.0f}}},/* A */ + /* -X up, +X down + +----+ + | +Y | + A----C----E----G----+ + | -X | +Z | +X | -Z | + +----D----F----H----+ + | -Y | + +----+ + 0.0 0.5 0.75 */ + {{/* 0, +Z */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.25f, 2.0f/3.0f}, /* C */ + /* 4, +X */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 2.0f/3.0f}, /* G */ + {0.50f, 2.0f/3.0f}, /* E */ + /* 8, +Y */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.25f, 3.0f/3.0f}, + {0.00f, 3.0f/3.0f}, + {0.00f, 2.0f/3.0f}, /* A */ + /* 12, -Z */ + {0.75f, 1.0f/3.0f}, /* H */ + {1.00f, 1.0f/3.0f}, + {1.00f, 2.0f/3.0f}, + {0.75f, 2.0f/3.0f}, /* G */ + /* 16, -Y */ + {0.75f, 0.0f/3.0f}, + {0.75f, 1.0f/3.0f}, /* H */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 0.0f/3.0f}, + /* 20, -X */ + {0.00f, 1.0f/3.0f}, + {0.25f, 1.0f/3.0f}, /* D */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.00f, 2.0f/3.0f}}},/* A */ + /* -X up, -Z down + +----+ + | +Y | + A----C----E----G----+ + | -X | +Z | +X | -Z | + +----D----F----H----B + | -Y | + +----+ + 0.0 0.75 1.0 */ + {{/* 0, +Z */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.25f, 2.0f/3.0f}, /* C */ + /* 4, +X */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 2.0f/3.0f}, /* G */ + {0.50f, 2.0f/3.0f}, /* E */ + /* 8, +Y */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.25f, 3.0f/3.0f}, + {0.00f, 3.0f/3.0f}, + {0.00f, 2.0f/3.0f}, /* A */ + /* 12, -Z */ + {0.75f, 1.0f/3.0f}, /* H */ + {1.00f, 1.0f/3.0f}, /* B */ + {1.00f, 2.0f/3.0f}, + {0.75f, 2.0f/3.0f}, /* G */ + /* 16, -Y */ + {1.00f, 1.0f/3.0f}, /* B */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 0.0f/3.0f}, + {1.00f, 0.0f/3.0f}, + /* 20, -X */ + {0.00f, 1.0f/3.0f}, + {0.25f, 1.0f/3.0f}, /* D */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.00f, 2.0f/3.0f}}},/* A */ + /* +Z up, +Z down + +----+ + | +Y | + +----C----E----G----+ + | -X | +Z | +X | -Z | + +----D----F----H----+ + | -Y | + +----+ + 0.25 0.5 */ + {{/* 0, +Z */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.25f, 2.0f/3.0f}, /* C */ + /* 4, +X */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 2.0f/3.0f}, /* G */ + {0.50f, 2.0f/3.0f}, /* E */ + /* 8, +Y */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.50f, 3.0f/3.0f}, + {0.25f, 3.0f/3.0f}, + /* 12, -Z */ + {0.75f, 1.0f/3.0f}, /* H */ + {1.00f, 1.0f/3.0f}, + {1.00f, 2.0f/3.0f}, + {0.75f, 2.0f/3.0f}, /* G */ + /* 16, -Y */ + {0.25f, 0.0f/3.0f}, + {0.50f, 0.0f/3.0f}, + {0.50f, 1.0f/3.0f}, /* F */ + {0.25f, 1.0f/3.0f}, /* D */ + /* 20, -X */ + {0.00f, 1.0f/3.0f}, + {0.25f, 1.0f/3.0f}, /* D */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.00f, 2.0f/3.0f}}}, + /* +Z up, +X down + +----+ + | +Y | + +----C----E----G----+ + | -X | +Z | +X | -Z | + +----D----F----H----+ + | -Y | + +----+ + 0.5 0.75 */ + {{/* 0, +Z */ + {0.25f, 1.0f/3.0f}, /* D */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.25f, 2.0f/3.0f}, /* C */ + /* 4, +X */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.75f, 1.0f/3.0f}, /* H */ + {0.75f, 2.0f/3.0f}, /* G */ + {0.50f, 2.0f/3.0f}, /* E */ + /* 8, +Y */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.50f, 2.0f/3.0f}, /* E */ + {0.50f, 3.0f/3.0f}, + {0.25f, 3.0f/3.0f}, + /* 12, -Z */ + {0.75f, 1.0f/3.0f}, /* H */ + {1.00f, 1.0f/3.0f}, + {1.00f, 2.0f/3.0f}, + {0.75f, 2.0f/3.0f}, /* G */ + /* 16, -Y */ + {0.75f, 0.0f/3.0f}, + {0.75f, 1.0f/3.0f}, /* H */ + {0.50f, 1.0f/3.0f}, /* F */ + {0.50f, 0.0f/3.0f}, + /* 20, -X */ + {0.00f, 1.0f/3.0f}, + {0.25f, 1.0f/3.0f}, /* D */ + {0.25f, 2.0f/3.0f}, /* C */ + {0.00f, 2.0f/3.0f}}}, +}; + +/* The tangent is the same for all four vertices in each face so it's just 6 + instead of 24. GCC 4.8 dies with "error: array must be initialized with a + brace-enclosed initializer" if it's `constexpr Vector4 Tangents[][6]`. + Turning the second dimension into a struct seems to work around the + problem. */ +/** @todo drop once GCC 4.8 is gone */ +constexpr struct { + Vector4 v[6]; +} Tangents[8]{ + /* All same. Well, tangents are *not* all same in this case. */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* Positive up, negative down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* -X up, -X down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 0.0f, 0.0f, 1.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 0.0f, 0.0f, 1.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* -X up, +Z down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 0.0f, 0.0f, 1.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* -X up, +X down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 0.0f, 0.0f, 1.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* -X up, -Z down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 0.0f, 0.0f, 1.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* +Z up, +Z down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ + /* +Z up, +X down */ + {{{ 1.0f, 0.0f, 0.0f, 1.0f}, /* +Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* +X */ + { 1.0f, 0.0f, 0.0f, 1.0f}, /* +Y */ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* -Z */ + { 0.0f, 0.0f, -1.0f, 1.0f}, /* -Y */ + { 0.0f, 0.0f, 1.0f, 1.0f}}},/* -X */ +}; + +} + +Trade::MeshData cubeSolid(const CubeFlags flags) { + const UnsignedInt textureCoordinateVariant = UnsignedByte(flags) >> 1; + CORRADE_ASSERT(textureCoordinateVariant <= Containers::arraySize(TextureCoordinates), + /* Since the enum isn't really a bitflag, implementing a debug printer + for it doesn't really make sense anyway. But be at least a bit + helpful and print the assumed TextureCoordinates flag value w/o the + other bits. */ + "Primitives::cubeSolid(): unrecognized texture coordinate option" << Debug::hex << UnsignedByte(flags & ~CubeFlag::Tangents), (Trade::MeshData{MeshPrimitive::Triangles, 0})); + CORRADE_ASSERT(!(flags & CubeFlag::Tangents) || textureCoordinateVariant, + "Primitives::cubeSolid(): a texture coordinate option has to be picked if tangents are enabled", (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Return the compile-time data if nothing extra is requested */ + if(!flags) + return cubeSolid(); + + /* Calculate attribute count and vertex size */ + std::size_t stride = sizeof(Vector3) + sizeof(Vector3); + std::size_t attributeCount = 2; + if(flags & CubeFlag::Tangents) { + stride += sizeof(Vector4); + ++attributeCount; + } + if(textureCoordinateVariant) { + stride += sizeof(Vector2); + ++attributeCount; + } + + /* Set up the layout */ + Containers::Array vertexData{NoInit, 24*stride}; + Containers::Array attributeData{attributeCount}; + std::size_t attributeIndex = 0; + std::size_t attributeOffset = 0; + + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.data() + attributeOffset), + 24, std::ptrdiff_t(stride)}; + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, positions}; + attributeOffset += sizeof(Vector3); + + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.data() + sizeof(Vector3)), + 24, std::ptrdiff_t(stride)}; + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, normals}; + attributeOffset += sizeof(Vector3); + + Containers::StridedArrayView1D tangents; + if(flags & CubeFlag::Tangents) { + tangents = Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + attributeOffset), + 24, std::ptrdiff_t(stride)}; + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Tangent, tangents}; + attributeOffset += sizeof(Vector4); + } + + Containers::StridedArrayView1D textureCoordinates; + if(textureCoordinateVariant) { + textureCoordinates = Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + attributeOffset), + 24, std::ptrdiff_t(stride)}; + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, textureCoordinates}; + attributeOffset += sizeof(Vector2); + } + + CORRADE_INTERNAL_ASSERT(attributeIndex == attributeCount); + CORRADE_INTERNAL_ASSERT(attributeOffset == stride); + + /* Fill the data */ + for(std::size_t i = 0; i != 24; ++i) { + positions[i] = VerticesSolid[i].position; + normals[i] = VerticesSolid[i].normal; + } + if(textureCoordinateVariant) { + for(std::size_t i = 0; i != 24; ++i) + textureCoordinates[i] = TextureCoordinates[textureCoordinateVariant - 1].v[i]; + } + if(flags & CubeFlag::Tangents) { + for(std::size_t i = 0; i != 6; ++i) + for(std::size_t j = 0; j != 4; ++j) + tangents[i*4 + j] = Tangents[textureCoordinateVariant - 1].v[i]; + } + + return Trade::MeshData{MeshPrimitive::Triangles, + Trade::DataFlag::Global, IndicesSolid, Trade::MeshIndexData{IndicesSolid}, + Utility::move(vertexData), Utility::move(attributeData)}; +} + +namespace { + /* Can't be just an array of Vector3 because MSVC 2015 is special. See Crosshair.cpp for details. */ constexpr struct VertexSolidStrip { diff --git a/src/Magnum/Primitives/Cube.h b/src/Magnum/Primitives/Cube.h index cfc6fdeb8..f67fe8571 100644 --- a/src/Magnum/Primitives/Cube.h +++ b/src/Magnum/Primitives/Cube.h @@ -27,28 +27,139 @@ */ /** @file - * @brief Function @ref Magnum::Primitives::cubeSolid(), @ref Magnum::Primitives::cubeSolidStrip(), @ref Magnum::Primitives::cubeWireframe() + * @brief Enum @ref Magnum::Primitives::CubeFlag, enum set @ref Magnum::Primitives::CubeFlags, function @ref Magnum::Primitives::cubeSolid(), @ref Magnum::Primitives::cubeSolidStrip(), @ref Magnum::Primitives::cubeWireframe() */ +#include + #include "Magnum/Primitives/visibility.h" #include "Magnum/Trade/Trade.h" namespace Magnum { namespace Primitives { +/** +@brief 3D cube flag +@m_since_latest + +@see @ref CubeFlags, @ref cubeSolid() +*/ +enum class CubeFlag: UnsignedByte { + /** + * Texture coordinates with a single image used for all faces, oriented in + * a way that makes the image upright and not mirrored if looking from the + * default +Z direction. Useful if all faces are meant to look the same. + * Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-all-same.svg + */ + TextureCoordinatesAllSame = 1 << 1, + + /** + * Texture coordinates with +X, +Y, +Z faces in the top row and -X, -Y and + * -Z in the bottom row, oriented in a way that makes the image upright and + * not mirrored if looking from the default +Z direction. Useful to have a + * different texture for each face but still make use of the whole texture + * area with no wasted space. Mutually exclusive with other + * `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-positive-up-negative-down.svg + */ + TextureCoordinatesPositiveUpNegativeDown = 2 << 1, + + /** + * Texture coordinates with both upper and lower face going from -X. + * Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-negative-x-up-negative-x-down.svg + */ + TextureCoordinatesNegativeXUpNegativeXDown = 3 << 1, + + /** + * Texture coordinates with upper face going from -X and lower face from + * +Z. Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-negative-x-up-positive-z-down.svg + */ + TextureCoordinatesNegativeXUpPositiveZDown = 4 << 1, + + /** + * Texture coordinates with upper face going from -X and lower face from + * +X. Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-negative-x-up-positive-x-down.svg + */ + TextureCoordinatesNegativeXUpPositiveXDown = 5 << 1, + + /** + * Texture coordinates with upper face going from -X and lower face from + * -Z. Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-negative-x-up-negative-z-down.svg + */ + TextureCoordinatesNegativeXUpNegativeZDown = 6 << 1, + + /** + * Texture coordinates with both upper face lower face going from +Z. + * Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-positive-z-up-positive-z-down.svg + */ + TextureCoordinatesPositiveZUpPositiveZDown = 7 << 1, + + /** + * Texture coordinates with upper face going from +Z and lower face from + * +X. Mutually exclusive with other `TextureCoordinates*` flags. + * + * @htmlinclude primitives-cube-positive-z-up-positive-x-down.svg + */ + TextureCoordinatesPositiveZUpPositiveXDown = 8 << 1, + + /** + * Generate four-component tangents. The last component can be used to + * reconstruct a bitangent as described in the documentation of + * @ref Trade::MeshAttribute::Tangent. Requires one of the + * `TextureCoordinates*` to be set in order to know the tangent direction. + */ + Tangents = 1 << 0, +}; + +/** +@brief 3D cube flags +@m_since_latest + +@see @ref circle2DSolid() +*/ +typedef Containers::EnumSet CubeFlags; + +CORRADE_ENUMSET_OPERATORS(CubeFlags) + /** @brief Solid 3D cube 2x2x2 cube, centered at origin. @ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedShort indices, interleaved @ref VertexFormat::Vector3 positions and flat @ref VertexFormat::Vector3 -normals. The returned instance references @ref Trade::DataFlag::Global data --- -pass the mesh through @ref MeshTools::copy() to get a mutable copy, if needed. +normals. If no @p flags are passed, the returned instance references +@ref Trade::DataFlag::Global data --- pass the mesh through +@ref MeshTools::copy() to get a mutable copy, if needed. @image html primitives-cubesolid.png width=256px -@see @ref cubeSolidStrip(), @ref cubeWireframe() +Expects that at most one `TextureCoordinates*` @ref CubeFlag is set. If +@ref CubeFlag::Tangents is set, expects that exactly one `TextureCoordinates*` +@ref CubeFlag is set as well. Rotate or mirror the resulting mesh texture +coordinates, positions or both to create remaining texture mapping variants. + +@see @ref cubeSolidStrip(), @ref cubeWireframe(), @ref MeshTools::transform3D(), + @ref MeshTools::transformTextureCoordinates2D() */ +#ifdef DOXYGEN_GENERATING_OUTPUT +Trade::MeshData cubeSolid(CubeFlags flags = {}); +#else +/* Make it possible to DCE the texture / tangent logic if no flags are used */ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeSolid(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeSolid(CubeFlags flags); +#endif /** @brief Solid 3D cube as a single strip diff --git a/src/Magnum/Primitives/Test/CMakeLists.txt b/src/Magnum/Primitives/Test/CMakeLists.txt index d2713afc1..6aa42e45b 100644 --- a/src/Magnum/Primitives/Test/CMakeLists.txt +++ b/src/Magnum/Primitives/Test/CMakeLists.txt @@ -32,7 +32,7 @@ corrade_add_test(PrimitivesAxisTest AxisTest.cpp LIBRARIES MagnumPrimitives) corrade_add_test(PrimitivesCapsuleTest CapsuleTest.cpp LIBRARIES MagnumPrimitivesTestLib) corrade_add_test(PrimitivesCircleTest CircleTest.cpp LIBRARIES MagnumPrimitivesTestLib) corrade_add_test(PrimitivesCrosshairTest CrosshairTest.cpp LIBRARIES MagnumPrimitives) -corrade_add_test(PrimitivesCubeTest CubeTest.cpp LIBRARIES MagnumPrimitives) +corrade_add_test(PrimitivesCubeTest CubeTest.cpp LIBRARIES MagnumPrimitivesTestLib) corrade_add_test(PrimitivesConeTest ConeTest.cpp LIBRARIES MagnumPrimitivesTestLib) corrade_add_test(PrimitivesCylinderTest CylinderTest.cpp LIBRARIES MagnumPrimitivesTestLib) corrade_add_test(PrimitivesGradientTest GradientTest.cpp LIBRARIES MagnumPrimitives) diff --git a/src/Magnum/Primitives/Test/CubeTest.cpp b/src/Magnum/Primitives/Test/CubeTest.cpp index 6d40180f2..6b5938b2d 100644 --- a/src/Magnum/Primitives/Test/CubeTest.cpp +++ b/src/Magnum/Primitives/Test/CubeTest.cpp @@ -24,11 +24,15 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include #include +#include #include "Magnum/Mesh.h" #include "Magnum/Math/Functions.h" -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/FunctionsBatch.h" /* minmax() */ +#include "Magnum/Math/Vector4.h" #include "Magnum/Primitives/Cube.h" #include "Magnum/Trade/MeshData.h" @@ -38,20 +42,263 @@ struct CubeTest: TestSuite::Tester { explicit CubeTest(); void solid(); + template void solidTextureCoordinates(); + void solidInvalid(); void solidStrip(); void solidStripGlsl(); void wireframe(); }; +const struct { + const char* name; + Containers::Optional flags; +} SolidData[]{ + {"", {}}, + {"explicit empty flags", CubeFlags{}} +}; + +enum class CubeEdge { + /* 0 is reserved */ + + /* Horizontal edges */ + BottomBack = 1, /* {0, -1, +1} */ + BottomFront, /* {0, -1, -1} */ + TopBack, /* {0, +1, +1} */ + TopFront, /* {0, +1, -1} */ + + /* Vertical edges */ + BackLeft, /* {-1, 0, +1} */ + BackRight, /* {+1, 0, +1} */ + FrontLeft, /* {-1, 0, -1} */ + FrontRight, /* {+1, 0, -1} */ + + /* "Depth" edges */ + BottomLeft, /* {-1, -1, 0} */ + BottomRight, /* {+1, -1, 0} */ + TopLeft, /* {-1, +1, 0} */ + TopRight, /* {+1, +1, 0} */ +}; + +Debug& operator<<(Debug& out, CubeEdge edge) { + switch(edge) { + #define _c(value) case CubeEdge::value: return out << #value; + _c(BottomBack) + _c(BottomFront) + _c(TopBack) + _c(TopFront) + _c(BackLeft) + _c(BackRight) + _c(FrontLeft) + _c(FrontRight) + _c(BottomLeft) + _c(BottomRight) + _c(TopLeft) + _c(TopRight) + #undef _c + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +} + +const struct { + const char* name; + CubeFlags flags; + /* +X, -X, +Y, -Y, +Z, -Z (same order as GL::CubeMapCoordinate) */ + Vector2 expectedCenters[6]; + /* Cases where less than 12 edges are shared have the rest zero-filled */ + CubeEdge expectedSharedEdges[12]; +} SolidTextureCoordinatesData[]{ + {"all same", CubeFlag::TextureCoordinatesAllSame, { + {0.5f, 0.5f}, + {0.5f, 0.5f}, + {0.5f, 0.5f}, + {0.5f, 0.5f}, + {0.5f, 0.5f}, + {0.5f, 0.5f} + }, { + /* (No shared edges in this case) */ + }}, + /* +----+----+----+ 1.0 + | +X | +Y | +Z | 0.75 + +----+----+----+ 0.5 + | -X | -Y | -Z | 0.25 + +----+----+----+ 0.0 + 0.0 0.333 0.667 1.0 + 0.167 0.5 0.833 */ + {"+ up, - down", CubeFlag::TextureCoordinatesPositiveUpNegativeDown, { + {0.16667f, 0.75f}, /* +X */ + {0.16667f, 0.25f}, /* -X */ + {0.5f, 0.75f}, /* +Y */ + {0.5f, 0.25f}, /* -Y */ + {0.83333f, 0.75f}, /* +Z */ + {0.83333f, 0.25f} /* -Z */ + }, { + /* (*Deliberately* no shared edges in this case either. They could be + but it'd mean some faces would be rotated, which is just weird. */ + }}, + /* +-----+ 1.0 + | +Y | 0.833 + +-tl--+-----+-----+-----+ 0.667 + | -X bl +Z br +X fr -Z | 0.5 + +-bl--+-----+-----+-----+ 0.333 + | -Y | 0.167 + +-----+ 0.0 + 0.0 0.25 0.5 0.75 1.0 + 0.125 0.375 0.625 0.875 */ + {"-X up, -X down", CubeFlag::TextureCoordinatesNegativeXUpNegativeXDown, { + {0.625f, 0.5f}, /* +X */ + {0.125f, 0.5f}, /* -X */ + {0.125f, 0.83333f}, /* +Y */ + {0.125f, 0.16667f}, /* -Y */ + {0.375f, 0.5f}, /* +Z */ + {0.875f, 0.5f} /* -Z */ + }, { + CubeEdge::TopLeft, + CubeEdge::BottomLeft, + CubeEdge::BackLeft, + CubeEdge::BackRight, + CubeEdge::FrontRight, + }}, + /* +-----+ + | +Y | + +-tl--+-----+-----+-----+ + | -X bl +Z br +X fr -Z | + +-----+-bb--+-----+-----+ + | -Y | + +-----+ + 0.25 0.5 + 0.375 */ + {"-X up, +Z down", CubeFlag::TextureCoordinatesNegativeXUpPositiveZDown, { + {0.625f, 0.5f}, /* +X */ + {0.125f, 0.5f}, /* -X */ + {0.125f, 0.83333f}, /* +Y */ + {0.375f, 0.16667f}, /* -Y */ + {0.375f, 0.5f}, /* +Z */ + {0.875f, 0.5f} /* -Z */ + }, { + CubeEdge::TopLeft, + CubeEdge::BackLeft, + CubeEdge::BottomBack, + CubeEdge::BackRight, + CubeEdge::FrontRight, + }}, + /* +-----+ + | +Y | + +-tl--+-----+-----+-----+ + | -X bl +Z br +X fr -Z | + +-----+-----+-br--+-----+ + | -Y | + +-----+ + 0.5 0.75 + 0.625 */ + {"-X up, +X down", CubeFlag::TextureCoordinatesNegativeXUpPositiveXDown, { + {0.625f, 0.5f}, /* +X */ + {0.125f, 0.5f}, /* -X */ + {0.125f, 0.83333f}, /* +Y */ + {0.625f, 0.16667f}, /* -Y */ + {0.375f, 0.5f}, /* +Z */ + {0.875f, 0.5f} /* -Z */ + }, { + CubeEdge::TopLeft, + CubeEdge::BackLeft, + CubeEdge::BackRight, + CubeEdge::BottomRight, + CubeEdge::FrontRight, + }}, + /* +-----+ + | +Y | + +-tl--+-----+-----+-----+ + | -X bl +Z br +X fr -Z | + +-----+-----+-----+-bf--+ + | -Y | + +-----+ + 0.75 1.0 + 0.875 */ + {"-X up, -Z down", CubeFlag::TextureCoordinatesNegativeXUpNegativeZDown, { + {0.625f, 0.5f}, /* +X */ + {0.125f, 0.5f}, /* -X */ + {0.125f, 0.83333f}, /* +Y */ + {0.875f, 0.16667f}, /* -Y */ + {0.375f, 0.5f}, /* +Z */ + {0.875f, 0.5f} /* -Z */ + }, { + CubeEdge::TopLeft, + CubeEdge::BackLeft, + CubeEdge::BackRight, + CubeEdge::FrontRight, + CubeEdge::BottomFront, + }}, + /* +-----+ + | +Y | + +-----+-tb--+-----+-----+ + | -X bl +Z br +X fr -Z | + +-----+-bb--+-----+-----+ + | -Y | + +-----+ + 0.25 0.5 + 0.375 */ + {"+Z up, +Z down", CubeFlag::TextureCoordinatesPositiveZUpPositiveZDown, { + {0.625f, 0.5f}, /* +X */ + {0.125f, 0.5f}, /* -X */ + {0.375f, 0.83333f}, /* +Y */ + {0.375f, 0.16667f}, /* -Y */ + {0.375f, 0.5f}, /* +Z */ + {0.875f, 0.5f} /* -Z */ + }, { + CubeEdge::TopBack, + CubeEdge::BackLeft, + CubeEdge::BackRight, + CubeEdge::BottomBack, + CubeEdge::FrontRight, + }}, + /* +-----+ + | +Y | + +-----+-tb--+-----+-----+ + | -X bl +Z br +X fr -Z | + +-----+-bb--+-br--+-----+ + | -Y | + +-----+ + 0.5 0.75 + 0.625 */ + {"+Z up, +X down", CubeFlag::TextureCoordinatesPositiveZUpPositiveXDown, { + {0.625f, 0.5f}, /* +X */ + {0.125f, 0.5f}, /* -X */ + {0.375f, 0.83333f}, /* +Y */ + {0.625f, 0.16667f}, /* -Y */ + {0.375f, 0.5f}, /* +Z */ + {0.875f, 0.5f} /* -Z */ + }, { + CubeEdge::TopBack, + CubeEdge::BackLeft, + CubeEdge::BackRight, + CubeEdge::FrontRight, + CubeEdge::BottomRight, + }}, +}; + + CubeTest::CubeTest() { - addTests({&CubeTest::solid, + addInstancedTests({&CubeTest::solid}, + Containers::arraySize(SolidData)); + + addInstancedTests({ + &CubeTest::solidTextureCoordinates, + &CubeTest::solidTextureCoordinates + }, Containers::arraySize(SolidTextureCoordinatesData)); + + addTests({&CubeTest::solidInvalid, &CubeTest::solidStrip, &CubeTest::solidStripGlsl, &CubeTest::wireframe}); } void CubeTest::solid() { - Trade::MeshData cube = Primitives::cubeSolid(); + auto&& data = SolidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData cube = data.flags ? + Primitives::cubeSolid(*data.flags) : + Primitives::cubeSolid(); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(cube.isIndexed()); @@ -65,6 +312,259 @@ void CubeTest::solid() { (Vector3{1.0f, 0.0f, 0.0f})); } +template T sampleQuad(const T& a, const T& b, const T& c, const T& d, Vector2 t) { + /* Assuming the vertex order is the following, which means the second lerp + has to be in a flipped direction in order to give expected result. + 3--2 + | | + 0--1 */ + return Math::lerp(Math::lerp(a, b, t[0]), + Math::lerp(d, c, t[0]), t[1]); +} + +template void CubeTest::solidTextureCoordinates() { + auto&& data = SolidTextureCoordinatesData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(flag == CubeFlag::Tangents ? "CubeFlag::Tangents" : ""); + + Trade::MeshData cube = Primitives::cubeSolid(data.flags|flag); + Containers::StridedArrayView1D positions = cube.attribute(Trade::MeshAttribute::Position); + Containers::StridedArrayView1D textureCoordinates = cube.attribute(Trade::MeshAttribute::TextureCoordinates); + + /* Same as in solid(), to verify basic sanity */ + CORRADE_COMPARE(cube.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cube.isIndexed()); + CORRADE_COMPARE(cube.indexCount(), 36); + CORRADE_COMPARE(cube.vertexCount(), 24); + CORRADE_COMPARE(cube.attributeCount(), 3 + (flag == CubeFlag::Tangents ? 1 : 0)); + CORRADE_COMPARE(cube.indices()[17], 11); + CORRADE_COMPARE(positions[4], (Vector3{1.0f, -1.0f, 1.0f})); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Normal)[6], (Vector3{1.0f, 0.0f, 0.0f})); + + /* Discover which groups of vertices correspond to which faces, in order + matching SolidTextureCoordinatesData::expectedCenters, so +X, -X, +Y, + -Y, +Z, -Z. This could be done just once but who cares, it's just a + test. It could also be hardcoded but that'll make the test tied too much + to the particular data, making it more likely that the test passes with + the data actually being completely wrong. */ + Vector3 faceCenters[6]{ + {+1.0f, 0.0f, 0.0f}, + {-1.0f, 0.0f, 0.0f}, + {0.0f, +1.0f, 0.0f}, + {0.0f, -1.0f, 0.0f}, + {0.0f, 0.0f, +1.0f}, + {0.0f, 0.0f, -1.0f}, + }; + UnsignedInt faceVertexOffsets[6]; + for(UnsignedInt face = 0; face != 6; ++face) { + CORRADE_ITERATION(face); + + Vector3 center = sampleQuad(positions[face*4 + 0], + positions[face*4 + 1], + positions[face*4 + 2], + positions[face*4 + 3], {0.5f, 0.5f}); + UnsignedInt candidate = 0; + for(; candidate != Containers::arraySize(faceCenters); ++candidate) { + if(center == faceCenters[candidate]) { + faceVertexOffsets[candidate] = face*4; + break; + } + } + CORRADE_VERIFY(candidate != Containers::arraySize(faceCenters)); + } + + /* Discover which groups of vertices correspond to which edges, in order + matching the CubeEdge enum above. Same as above, this could be done just + once, or hardcoded, etc., but it's not. */ + Vector3 edgeCenters[12] { + {0.0f, -1.0f, +1.0f}, /* BottomBack */ + {0.0f, -1.0f, -1.0f}, /* BottomFront */ + {0.0f, +1.0f, +1.0f}, /* TopBack */ + {0.0f, +1.0f, -1.0f}, /* TopFront */ + + {-1.0f, 0.0f, +1.0f}, /* BackLeft */ + {+1.0f, 0.0f, +1.0f}, /* BackRight */ + {-1.0f, 0.0f, -1.0f}, /* FrontLeft */ + {+1.0f, 0.0f, -1.0f}, /* FrontRight */ + + {-1.0f, -1.0f, 0.0f}, /* BottomLeft */ + {+1.0f, -1.0f, 0.0f}, /* BottomRight */ + {-1.0f, +1.0f, 0.0f}, /* TopLeft */ + {+1.0f, +1.0f, 0.0f}, /* TopRight */ + }; + /* Each of 12 edges is shared by exactly 2 faces */ + Vector2ui edgeVertices[12][2]; + for(UnsignedInt face = 0; face != 6; ++face) { + CORRADE_ITERATION(face); + + /* Four edges of the quad. Assuming ordering like below, if that + wouldn't be the case, the CORRADE_VERIFY after would fail. + 3--2 + | | + 0--1 */ + for(Vector2ui edge: { + Vector2ui{face*4 + 0, face*4 + 1}, + Vector2ui{face*4 + 1, face*4 + 2}, + Vector2ui{face*4 + 2, face*4 + 3}, + Vector2ui{face*4 + 3, face*4 + 0} + }) { + Vector3 center = Math::lerp(positions[edge[0]], + positions[edge[1]], 0.5f); + + UnsignedInt candidate = 0; + for(; candidate != Containers::arraySize(edgeCenters); ++candidate) { + if(center == edgeCenters[candidate]) { + if(edgeVertices[candidate][0].isZero()) + edgeVertices[candidate][0] = edge; + else if(edgeVertices[candidate][1].isZero()) + edgeVertices[candidate][1] = edge; + else CORRADE_FAIL("Too many shared edges."); + break; + } + } + CORRADE_ITERATION(edge); + CORRADE_VERIFY(candidate != Containers::arraySize(edgeCenters)); + } + } + /* At this point, if neither the above CORRADE_VERIFY() nor the + CORRADE_FAIL() fire, for each of the 6 faces the 4 edges were assigned, + filling all 24 array entries */ + + /* For each face verify that the sampled texture coordinates at the center + match the expectation */ + for(UnsignedInt face = 0; face != 6; ++face) { + UnsignedInt vertexOffset = faceVertexOffsets[face]; + CORRADE_ITERATION("face" << face << "at offset" << vertexOffset); + Vector2 center = sampleQuad( + textureCoordinates[vertexOffset + 0], + textureCoordinates[vertexOffset + 1], + textureCoordinates[vertexOffset + 2], + textureCoordinates[vertexOffset + 3], {0.5f, 0.5f}); + CORRADE_COMPARE(center, data.expectedCenters[face]); + } + + /* Verify that the expected shared edges indeed have the same texture + coordinates for both faces */ + for(CubeEdge edge: data.expectedSharedEdges) { + /* When we reach an edge that's zero it's the end of the list */ + if(edge == CubeEdge{}) + break; + + /* Sanity check -- the two edges should be filled and have contents + that aren't the same */ + const Vector2ui(&vertices)[2] = edgeVertices[UnsignedInt(edge) - 1]; + CORRADE_ITERATION(edge << "edge with vertices" << Debug::packed << vertices[0] << "and" << Debug::packed << vertices[1]); + CORRADE_VERIFY(!vertices[0].isZero() && + !vertices[1].isZero()); + CORRADE_VERIFY(vertices[0] != vertices[1] && + vertices[0] != vertices[1].flipped()); + + /* The edge should match in one or the other direction */ + Vector2 a0 = textureCoordinates[vertices[0][0]]; + Vector2 a1 = textureCoordinates[vertices[0][1]]; + Vector2 b0 = textureCoordinates[vertices[1][0]]; + Vector2 b1 = textureCoordinates[vertices[1][1]]; + CORRADE_VERIFY((a0 == b0 && a1 == b1) || + (a0 == b1 && a1 == b0)); + } + + /* The texture coordinates should always span the whole [0, 0] to [1, 1] + range. That may mean the faces won't be square if using a square + texture, but in practice the texture would have a size matching the + texture coordinate layout, so e.g. with a 4:3 ratio for the + NegativeXUpNegativeXDown variant. */ + CORRADE_COMPARE(Math::minmax(textureCoordinates), Containers::pair(Vector2{0.0f}, Vector2{1.0f})); + + /* If tangents are enabled, check their properties also */ + if(flag == CubeFlag::Tangents) { + Containers::StridedArrayView1D normals = cube.attribute(Trade::MeshAttribute::Normal); + Containers::StridedArrayView1D tangents = cube.attribute(Trade::MeshAttribute::Tangent); + + /* Normals and tangents should be the same for all vertices in a face, + and perpendicular in all cases */ + for(UnsignedInt face = 0; face != 6; ++face) { + CORRADE_ITERATION(face); + CORRADE_COMPARE(Math::dot(normals[face*4], tangents[face*4].xyz()), 0.0f); + CORRADE_COMPARE(normals[face*4].dot(), 1.0f); + CORRADE_COMPARE(tangents[face*4].xyz().dot(), 1.0f); + CORRADE_COMPARE(Math::abs(tangents[face*4].w()), 1.0f); + for(UnsignedInt vertex = 1; vertex != 4; ++vertex) { + CORRADE_ITERATION(vertex); + CORRADE_COMPARE(normals[face*4 + vertex], normals[face*4]); + CORRADE_COMPARE(tangents[face*4 + vertex], tangents[face*4]); + } + } + + /* For each face, sample in a position off center on X and Y */ + for(UnsignedInt face = 0; face != 6; ++face) { + CORRADE_ITERATION(face); + + Vector3 center = sampleQuad( + positions[face*4 + 0], positions[face*4 + 1], + positions[face*4 + 2], positions[face*4 + 3], {0.5f, 0.5f}); + Vector2 centerTexture = sampleQuad( + textureCoordinates[face*4 + 0], textureCoordinates[face*4 + 1], + textureCoordinates[face*4 + 2], textureCoordinates[face*4 + 3], + {0.5f, 0.5f}); + Vector3 tangent = tangents[face*4].xyz(); + Vector3 bitangent = Math::cross(normals[face*4], + tangents[face*4].xyz())*tangents[face*4].w(); + + Vector3 offset[]{ + sampleQuad(positions[face*4 + 0], positions[face*4 + 1], + positions[face*4 + 2], positions[face*4 + 3], + {0.75f, 0.5f}), + sampleQuad(positions[face*4 + 0], positions[face*4 + 1], + positions[face*4 + 2], positions[face*4 + 3], + {0.5f, 0.75f}) + }; + Vector2 offsetTexture[]{ + sampleQuad(textureCoordinates[face*4 + 0], textureCoordinates[face*4 + 1], + textureCoordinates[face*4 + 2], textureCoordinates[face*4 + 3], + {0.75f, 0.5f}), + sampleQuad(textureCoordinates[face*4 + 0], textureCoordinates[face*4 + 1], + textureCoordinates[face*4 + 2], textureCoordinates[face*4 + 3], + {0.5f, 0.75f}) + }; + + for(Int i: {0, 1}) { + CORRADE_ITERATION(i); + + /* If the shift is in direction of tangent, texture coordinates + should be the same in Y and different with a matching sign + in X */ + if(Math::notEqual(Math::dot(offset[i] - center, tangent), 0.0f)) { + Vector2 delta = offsetTexture[i] - centerTexture; + CORRADE_COMPARE(delta.y(), 0.0f); + CORRADE_COMPARE(Math::sign(delta.x()), Math::sign(Math::dot(offset[i] - center, tangent))); + + /* If the shift is in direction of bitangent, texture + coordinates should be the same in X and different with a + matching sign in Y */ + } else if(Math::notEqual(Math::dot(offset[i] - center, bitangent), 0.0f)) { + Vector2 delta = offsetTexture[i] - centerTexture; + CORRADE_COMPARE(delta.x(), 0.0f); + CORRADE_COMPARE(Math::sign(delta.y()), Math::sign(Math::dot(offset[i] - center, bitangent))); + + } else CORRADE_FAIL(Debug::packed << offset[i] - center << "is parallel to neither" << Debug::packed << tangent << "nor" << Debug::packed << bitangent); + } + } + } +} + +void CubeTest::solidInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Containers::String out; + Error redirectError{&out}; + Primitives::cubeSolid(CubeFlag::Tangents); + Primitives::cubeSolid(CubeFlag(UnsignedInt(CubeFlag::TextureCoordinatesPositiveZUpPositiveXDown) + 2)); + CORRADE_COMPARE_AS(out, + "Primitives::cubeSolid(): a texture coordinate option has to be picked if tangents are enabled\n" + "Primitives::cubeSolid(): unrecognized texture coordinate option 0x12\n", + TestSuite::Compare::String); +} + void CubeTest::solidStrip() { Trade::MeshData cube = Primitives::cubeSolidStrip();