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();