Operations with matrices and vectors
Introduction to essential classes of the graphics pipeline.
Matrices and vectors are the most important part of graphics programming and one of Magnum goals is to make their usage as intuitive as possible.
Matrix and vector classes
Magnum has three main matrix and vector classes — a Math::
Each subclass brings some specialization to its superclass. The Math::
There are also even more specialized subclasses, e.g. Math::
Commonly used types have convenience aliases in the Magnum namespace, so you can write e.g. Vector3i instead of Math::
Constructing matrices and vectors
The default constructors of Math::
Matrix2x3 a; // zero-filled Vector3i b; // zero-filled Matrix3 c; // diagonal set to 1.0f Matrix3 d{Math::IdentityInit}; // diagonal set to 1.0f Matrix3 e{Math::ZeroInit}; // zero-filled
The most common and most efficient way to create a vector is to pass all values to the constructor. A matrix is created by passing all column vectors to the constructor. The constructors check correct number of passed arguments 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 a vector or a matrix with a single value, or specify just values on the matrix diagonal:
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 which are 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 a C-style array. The function performs a simple type cast without copying anything, 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 colors easier, their behavior is a bit different with a richer feature set. Implicit construction of Math::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}
Similar to axes and scales in vectors, you can create single color shades too:
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}
There are also builtin colorspace conversion functions — it's possible to create a RGB color from a HSV value, a linear color value from a sRGB representation, or convert from CIE XYZ / xyY. And the other way as well:
auto fadedRed = Color3::fromHsv({219.0_degf, 0.50f, 0.57f}); auto linear = Color3::fromSrgbInt(0x33b27f); // {0.2f, 0.7f, 0.5f} auto white = Color3::fromXyz({0.950456f, 1.0f, 1.08906f}); UnsignedInt srgb = linear.toSrgbInt(); // 0x33b27f
Finally, the 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 components of vectors can be accessed using square brackets:
Matrix3x2 a; a[2] /= 2.0f; // third column 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 access is slower due to the matrix being stored in column-major order:
Vector3 c = a.row(1); // second row
Fixed-size vector subclasses have functions for accessing named components and subparts using either xyzw
or rgba
:
Vector4i a; Int x = a.x(); a.y() += 5; Vector3i xyz = a.xyz(); xyz.xy() *= 5;
For more involved operations with components there are the Math::
Vector4i orig{-1, 2, 3, 4}; Vector4i bgra = Math::gather<'b', 'g', 'r', 'a'>(orig); // {3, 2, -1, 4} Math::Vector<6, Int> w10xyz = Math::gather<'w', '1', '0', 'x', 'y', 'z'>(orig); // {4, 1, 0, -1, 2, 3} Vector4 vec{1.5f, 3.0f, 0.1f, 1.1f}; Vector2 coords{5.0f, -2.0f}; Math::scatter<'z', 'w'>(vec, coords); // {1.5f, 3.0f, 5.0f, -2.0f}
Converting between different underlying types
All classes in Math namespace can be constructed from an instance with a different underlying type (e.g. convert between integer and floating-point type or betweeen Float, Double and Half). Unlike with plain C++ data types, the conversion is explicit. That might sound inconvenient at first, but performing the conversion explicitly avoids common issues like precision loss (or, on the other hand, expensive computation with unnecessarily high precision).
To further emphasise the intent of conversion (so it doesn't look like an accident or a 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 floats into integers and unpacking them back use the 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 a scalar with vector:
Vector3 a{1.0f, 2.0f, 3.0f}; Vector3 b = a*5.0f - Vector3{3.0f, 0.5f, 7.5f}; // {2.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 retain an integral result. In Magnum, all multiplication/division operations involving integral vectors will return an integer result, you need to convert both arguments to the same floating-point type to have a 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 equivalent to 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. For a sum or product of components inside a vector you can use sum() and product() instead:
Float a = Vector3{1.5f, 0.3f, 8.0f}.sum(); // 9.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 a Math::
BitVector3 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 and various interpolation and (de)normalization functionality:
Vector3 a{5.5f, -0.3f, 75.1f}; Vector3 b = Math::round(a); // {6.0f, 0.0f, 75.0f} Vector3 c = Math::abs(a); // {5.5f, -0.3f, 75.1f} 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 a sane and maintainable size. Instead, you can reinterpret the matrix as a vector and do the operation on it (and vice versa) — because you get a reference that way, the operation will affect the original data:
Matrix3x2 mat; Math::Vector<6, Float> vec = mat.toVector(); // ... mat = Matrix3x2::fromVector(vec);
Note that all component-wise functions in the Math namespace also work for scalars — and on the special Deg / Rad types too.
Containers::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 Deg c = Math::clamp(value, 25.0_degf, 55.0_degf);
For types with units the only exception are power functions such as Math::
Linear algebra operations
Also available are common linear algebra operations — the dot or cross product, vector, reflection or angle calculation. These are mostly available as free functions in the Math namespace, with more advanced functionality such as QR or SVD decomposition in Math::
Float zero = Math::dot(Vector3::xAxis(), Vector3::yAxis()); Vector3 zAxis = Math::cross(Vector3::xAxis(), Vector3::yAxis()); Deg ninety = Math::angle(Vector3::xAxis(), Vector3::zAxis());
Matrices are column-major and vectors are columns
For consistency with GLSL, Magnum stores matrices as column-major (the vectors are columns). This naturally has some implications and it may differ from what you're used to from linear algebra or other graphics toolkits:
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 the requirement that you must pass column vectors directly:
Math::Matrix3<Int> mat{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}}; // first column is {0, 1, 2}
Element access order is also column-major, thus the bracket operator accesses columns. The returned vector also has its own bracket operator, which then indexes 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 the user to operate with transposed matrices or use the slower non-transposed alternative of the algorithm.
Note that the Corrade::