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