Operations with matrices and vectors
Introduction to essential classes of the graphics pipeline.
Contents
Matrices and vectors are the most important part of graphics programming and one of goals of Magnum is to make their usage as intuitive as possible. They are contained in Math namespace and common variants also have aliases in root Magnum namespace. See documentation of these namespaces for more information about usage with CMake.
Matrix and vector classes
Magnum has three main matrix and vector classes: Math::
Each subclass brings some specialization to its superclass. For the most common vector and matrix sizes there are specialized classes Math::
There are also even more specialized subclasses, e.g. Math::
Commonly used types have convenience aliases in Magnum namespace, so you can write e.g. Vector3i instead of Math::
Constructing matrices and vectors
Default constructors of Math::
Matrix2x3 a; // zero-filled Vector3i b; // zero-filled Matrix3 identity; // diagonal set to 1 Matrix3 zero{Math::ZeroInit}; // zero-filled
Most common and most efficient way to create vector is to pass all values to constructor, matrix is created by passing all column vectors to the constructor. All constructors check number of passed arguments and the errors are catched at compile time.
Vector3i vec{0, 1, 2}; Matrix3 mat{{0.0f, 1.9f, 2.2f}, {3.5f, 4.0f, 5.1f}, {6.0f, 7.3f, 8.0f}};
You can specify all components of vector or whole diagonal of square matrix with single value or create diagonal matrix from vector:
Matrix3 diag{Math::IdentityInit, 2.0f}; // diagonal is 2.0f, zeros elsewhere Vector3i fill(10); // {10, 10, 10} auto diag2 = Matrix3::fromDiagonal({3.0f, 2.0f, 1.0f});
There are also shortcuts to create a vector with all but one component set to zero or one, useful for transformations:
auto x = Vector3::xAxis(); // {1.0f, 0.0f, 0.0f} auto y = Vector2::yAxis(3.0f); // {0.0f, 3.0f} auto z = Vector3::zScale(3.0f); // {1.0f, 1.0f, 3.0f}
It is also possible to create matrices and vectors from an C-style array. The function does simple type cast without any copying, so it's possible to conveniently operate on the array itself:
Int mat[]{ 2, 4, 6, 1, 3, 5 }; Math::Matrix2x3<Int>::from(mat) *= 2; // { 4, 8, 12, 2, 6, 10 }
To make handling of colors easier, their behavior is a bit different with a richer feature set. Implicit construction of Color4 from Color3 will set the alpha to full value (thus 1.0f
for Color4 and 255
for Color4ub):
Color4 a = Color3{0.2f, 0.7f, 0.5f}; // {0.2f, 0.7f, 0.5f, 1.0f} Color4ub b = Color3ub{0x33, 0xb2, 0x7f}; // {0x33, 0xb2, 0x7f, 0xff}
Similarly to axes in vectors, you can create single color shades too, or create a RGB color from HSV representation:
auto green = Color3::green(); // {0.0f, 1.0f, 0.0f} auto cyan = Color4::cyan(0.5f, 0.95f); // {0.5f, 1.0f, 1.0f, 0.95f} auto fadedRed = Color3::fromHsv(219.0_degf, 0.50f, 0.57f);
Lastly, namespace Math::
Color3ub a = 0x33b27f_rgb; // {0x33, 0xb2, 0x7f} Color4 b = 0x33b27fcc_rgbaf; // {0.2f, 0.7f, 0.5f, 0.8f} Color4 c = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f}
Accessing matrix and vector components
Column vectors of matrices and vector components can be accessed using square brackets:
Matrix3x2 a; a[2] /= 2.0f; // third column (column major indexing, see explanation below) a[0][1] = 5.3f; // first column, second element Vector3i b; b[1] = 1; // second element
Row vectors can be accessed too, but only for reading, and the access is slower due to the way the matrix is stored (see explanation below):
Vector3 c = a.row(1); // second row
Fixed-size vector subclasses have functions for accessing named components and subparts:
Vector4i a; Int x = a.x(); a.y() += 5; Vector3i xyz = a.xyz(); xyz.xy() *= 5;
Color3 and Color4 name their components rgba
instead of xyzw
.
For more involved operations with components there is the Math::
Vector4i orig{-1, 2, 3, 4}; Vector4i bgra = Math::swizzle<'b', 'g', 'r', 'a'>(orig); // { 3, 2, -1, 4 } Math::Vector<6, Int> w10xyz = Math::swizzle<'w', '1', '0', 'x', 'y', 'z'>(orig); // { 4, 1, 0, -1, 2, 3 }
Converting between different underlying types
All vector, matrix and other classes in Math namespace are able to be constructed from an instance with different underlying type (e.g. convert between integer and floating-point or betweeen Float and Double). Unlike with plain C++ data types, the conversion is done via explicit constructor. That might sound inconvenient, but doing the conversion explicitly avoids common issues like precision loss (or, on the other hand, doing computations in unnecessarily high precision).
To further emphasise the intent of conversion (so it doesn't look like accident or typo), you are encouraged to use auto b = Type{a}
instead of Type b{a}
.
Vector3 a{2.2f, 0.25f, -5.1f}; //Vector3i b = a; // error, implicit conversion not allowed auto c = Vector3i{a}; // {2, 0, -5} auto d = Vector3d{a}; // {2.2, 0.25, -5.1}
For packing and unpacking there are Math::
Color3 a{0.8f, 1.0f, 0.3f}; auto b = Math::pack<Color3ub>(a); // {204, 255, 76} Color3ub c{64, 127, 89}; auto d = Math::unpack<Color3>(c); // {0.251f, 0.498f, 0.349}
See below for more information about other available component-wise operations.
Operations with matrices and vectors
Vectors can be added, subtracted, negated and multiplied or divided with scalars, as is common in mathematics, Magnum also adds the ability to divide scalar with vector:
Vector3 a{1.0f, 2.0f, 3.0f}; Vector3 b = a*5.0f - Vector3{3.0f, -0.5f, -7.5f}; // {5.0f, 9.5f, 7.5f} Vector3 c = 1.0f/a; // {1.0f, 0.5f, 0.333f}
As in GLSL, vectors can be also multiplied or divided component-wise:
Vector3 a{1.0f, 2.0f, 3.0f}; Vector3 b = a*Vector3{-0.5f, 2.0f, -7.0f}; // {-0.5f, 4.0f, -21.0f}
When working with integral vectors (i.e. 24bit RGB values), it is often desirable to multiply them with floating-point values but with integral result. In Magnum, all multiplication/division operations involving integral vectors will have integral result, you need to convert both arguments to the same floating-point type to have floating-point result.
Color3ub color{80, 116, 34}; Color3ub lighter = color*1.5f; // {120, 174, 51} Vector3i a{4, 18, -90}; Vector3 multiplier{2.2f, 0.25f, 0.1f}; Vector3i b = a*multiplier; // {8, 4, -9} Vector3 c = Vector3(a)*multiplier; // {8.0f, 4.5f, -9.0f}
You can also use all bitwise operations on integral vectors:
Vector2i size{256, 256}; Vector2i mipLevel3Size = size >> 3; // {32, 32}
Matrices can be added, subtracted and multiplied with matrix multiplication.
Matrix3x2 a; Matrix3x2 b; Matrix3x2 c = a + (-b); Matrix2x3 d; Matrix2x2 e = b*d; Matrix3x3 f = d*b;
You can also multiply (properly sized) vectors with matrices. These operations are just convenience shortcuts for multiplying with single-column matrices:
Matrix3x4 a; Vector3 b; Vector4 c = a*b; Math::RectangularMatrix<4, 1, Float> d; Matrix4x3 e = b*d;
Component-wise and inter-vector operations
As shown above, vectors can be added and multiplied component-wise using the +
or *
operator. You can use sum() and product() for sum or product of components in one vector:
Float a = Vector3{1.5f, 0.3f, 8.0f}.sum(); // 8.8f Int b = Vector3i{32, -5, 7}.product(); // 1120
Component-wise minimum and maximum of two vectors can be done using Math::
Vector3i a{-5, 7, 24}; Vector3i b{8, -2, 12}; Vector3i min = Math::min(a, b); // {-5, -2, 12} Int max = a.max(); // 24
The vectors can be also compared component-wise, the result is returned in Math::
Math::BoolVector<3> largerOrEqual = a >= b; // {false, true, true} bool anySmaller = (a < b).any(); // true bool allLarger = (a > b).all(); // false
There are also function for component-wise rounding, sign operations, square root, various interpolation and (de)normalization functionality:
Vector3 a{5.5f, -0.3f, 75.0f}; Vector3 b = Math::round(a); // {5.0f, 0.0f, 75.0f} Vector3 c = Math::abs(a); // {5.5f, -0.3f, 75.0f} Vector3 d = Math::clamp(a, -0.2f, 55.0f); // {5.5f, -0.2f, 55.0f}
Component-wise functions are implemented only for vectors and not for matrices to keep the math library in sane and maintainable size. Instead, you can reinterpret the matrix as vector and do the operation on it (and vice versa):
Matrix3x2 mat; Math::Vector<6, Float> vec = mat.toVector(); // ... mat = Matrix3x2::fromVector(vec);
Note that all component-wise functions in Math namespace work also for scalars:
std::pair<Int, Int> minmax = Math::minmax(24, -5); // -5, 24 Int a = Math::lerp(0, 360, 0.75f); // 270 auto b = Math::pack<UnsignedByte>(0.89f); // 226
Matrices are column-major and vectors are columns
OpenGL matrices are column-major, thus it is reasonable to have matrices in Magnum also column major (and vectors as columns). This has naturally some implications and it may differ from what is common in mathematics:
Order of template arguments in specification of Math::
RectangularMatrix is also column-major: Math::RectangularMatrix<2, 5, Int> mat; // two columns, five rows
Order of components in matrix constructors is also column-major, further emphasized by requirement that you have to pass directly column vectors:
Math::Matrix3<Int> mat{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}}; // first column is {0, 1, 2}
Element accessing order is also column-major, thus the bracket operator is accessing columns. Returned vector has also its own bracket operator, which is then indexing rows.
mat[0] *= 2; // first column mat[2][0] = 5; // first element of third column
- Various algorithms which commonly operate on matrix rows (such as Gauss-Jordan elimination) have faster alternatives which operate on columns. It's then up to user decision to operate with transposed matrices or use the slower non-transposed alternative of the algorithm.