linalg.h
is a single header, public domain, short vector math library for C++. It is inspired by the syntax of popular shading and compute languages and is intended to serve as a lightweight alternative to projects such as GLM, Boost.QVM or Eigen in domains such as computer graphics, computational geometry, and physical simulation. It allows you to easily write programs like the following:
#include <linalg.h>
using namespace linalg::aliases;
// Compute the coefficients of the equation of a plane containing points a, b, and c
float4 compute_plane(float3 a, float3 b, float3 c)
{
float3 n = cross(b-a, c-a);
return {n, -dot(n,a)};
}
linalg.h
aims to be:
- Lightweight: The library is defined in a single header file which is less than a thousand lines of code.
- Dependency free: There are no dependencies beyond a compliant C++11 compiler and a small subset of the standard library.
- Standards compliant: Almost all operations are free of undefined behavior and can be evaluated in a
constexpr
context. - Generic: All types and operations are parameterized over scalar type, and can be mixed within expressions. Type promotion rules roughly match the C standard.
- Consistent: Named functions and overloaded operators perform the same conceptual operation on all data types for which they are supported.
- Complete: There are very few restrictions on which operations may be applied to which data types.
- Easy to integrate: The library defines no symbols in the public namespace, and provides a mechanism for defining implicit conversions to external or user-provided data types.
The documentation for v2.2
is still in progress.
linalg::vec<T,M>
defines a fixed-length vector containing exactly M
elements of type T
. Convenience aliases such as float3
, float4
, or int2
are provided in the linalg::aliases
namespace. This data structure can be used to store a wide variety of types of data, including geometric vectors, points, homogeneous coordinates, plane equations, colors, texture coordinates, or any other situation where you need to manipulate a small sequence of numbers. As such, vec<T,M>
is supported by a set of algebraic and component-wise functions, as well as a set of standard reductions.
vec<T,M>
:
- is
DefaultConstructible
:float3 v; // v contains 0,0,0
- is constructible from
M
elements of typeT
:float3 v {1,2,3}; // v contains 1,2,3
- is
CopyConstructible
andCopyAssignable
:float3 v {1,2,3}; // v contains 1,2,3 float3 u {v}; // u contains 1,2,3 float3 w; // w contains 0,0,0 w = u; // w contains 1,2,3
- is
EqualityComparable
andLessThanComparable
:if(v == y) cout << "v and u contain equal elements in the same positions" << endl; if(v < u) cout << "v precedes u lexicographically" << endl;
- is explicitly constructible from a single element of type
T
:float3 v = float3{4}; // v contains 4,4,4
- is explicitly constructible from a
vec<U,M>
of some other typeU
:float3 v {1.1f,2.3f,3.5f}; // v contains 1.1,2.3,3.5 int3 u = int3{v}; // u contains 1,2,3
- has fields
x,y,z,w
:float y = point.y; // y contains second element of point pixel.w = 0.5; // fourth element of pixel set to 0.5 float s = tc.x; // s contains first element of tc
- supports indexing:
float x = v[0]; // x contains first element of v v[2] = 5; // third element of v set to 5
- supports unary operators
+
,-
,!
and~
in component-wise fashion:auto v = -float{2,3}; // v is float2{-2,-3}
- supports binary operators
+
,-
,*
,/
,%
,|
,&
,^
,<<
and>>
in component-wise fashion:auto v = float2{1,1} + float2{2,3}; // v is float2{3,4}
- supports binary operators with a scalar on the left or the right:
auto v = 2 * float3{1,2,3}; // v is float3{2,4,6} auto u = float3{1,2,3} + 1; // u is float3{2,3,4}
- supports operators
+=
,-=
,*=
,/=
,%=
,|=
,&=
,^=
,<<=
and>>=
with vectors or scalars on the right:float2 v {1,2}; v *= 3; // v is float2{3,6}
- supports operations on mixed element types:
auto v = float3{1,2,3} + int3{4,5,6}; // v is float3{5,7,9}
- supports range-based for:
for(auto elem : float3{1,2,3}) cout << elem << ' '; // prints "1 2 3 "
- has a flat memory layout:
float3 v {1,2,3}; float * p = v.data(); // &v[i] == p+i p[1] = 4; // v contains 1,4,3
linalg::mat<T,M,N>
defines a fixed-size matrix containing exactly M
rows and N
columns of type T
, in column-major order. Convenience aliases such as float4x4
or double3x3
are provided in the linalg::aliases
namespace. This data structure is supported by a set of algebraic functions and component-wise functions, as well as a set of standard reductions.
mat<T,M,N>
:
-
float2x2 m; // m contains columns 0,0; 0,0
-
is constructible from
N
columns of typevec<T,M>
:float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4
-
is constructible from
linalg::identity
:float3x3 m = linalg::identity; // m contains columns 1,0,0; 0,1,0; 0,0,1
-
is
CopyConstructible
andCopyAssignable
:float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 float2x2 n {m}; // n contains columns 1,2; 3,4 float2x2 p; // p contains columns 0,0; 0,0 p = n; // p contains columns 1,2; 3,4
-
is
EqualityComparable
andLessThanComparable
:if(m == n) cout << "m and n contain equal elements in the same positions" << endl; if(m < n) cout << "m precedes n lexicographically when compared in column-major order" << endl;
-
is explicitly constructible from a single element of type
T
:float2x2 m {5}; // m contains columns 5,5; 5,5
-
is explicitly constructible from a
mat<U,M,N>
of some other typeU
:float2x2 m {int2x2{{5,6},{7,8}}}; // m contains columns 5,6; 7,8
-
supports indexing into columns:
float2x3 m {{1,2},{3,4},{5,6}}; // m contains columns 1,2; 3,4; 5,6 float2 c = m[0]; // c contains 1,2 m[1] = {7,8}; // m contains columns 1,2; 7,8; 5,6
-
supports retrieval (but not assignment) of rows:
float2x3 m {{1,2},{3,4},{5,6}}; // m contains columns 1,2; 3,4; 5,6 float3 r = m.row(1); // r contains 2,4,6
-
supports unary operators
+
,-
,!
and~
in component-wise fashion:float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 float2x2 n = -m; // n contains columns -1,-2; -3,-4
-
supports binary operators
+
,-
,*
,/
,%
,|
,&
,^
,<<
and>>
in component-wise fashion:float2x2 a {{0,0},{2,2}}; // a contains columns 0,0; 2,2 float2x2 b {{1,2},{1,2}}; // b contains columns 1,2; 1,2 float2x2 c = a + b; // c contains columns 1,2; 3,4
-
supports binary operators with a scalar on the left or the right:
auto m = 2 * float2x2{{1,2},{3,4}}; // m is float2x2{{2,4},{6,8}}
-
supports operators
+=
,-=
,*=
,/=
,%=
,|=
,&=
,^=
,<<=
and>>=
with matrices or scalars on the right:float2x2 v {{5,4},{3,2}}; v *= 3; // v is float2x2{{15,12},{9,6}}
-
supports operations on mixed element types:
-
supports range-based for over columns
-
has a flat memory layout
-
cross(vec<T,3> a, vec<T,3> b) -> vec<T,3>
is the cross or vector product of vectorsa
andb
cross(vec<T,2> a, vec<T,2> b) -> T
is shorthand forcross({a.x,a.y,0}, {b.x,b.y,0}).z
cross(T a, vec<T,2> b) -> vec<T,2>
is shorthand forcross({0,0,a.z}, {b.x,b.y,0}).xy()
cross(vec<T,2> a, T b) -> vec<T,2>
is shorthand forcross({a.x,a.y,0}, {0,0,b.z}).xy()
-
dot(vec<T,M> a, vec<T,M> b) -> T
is the dot or inner product of vectorsa
andb
-
length(vec<T,M> a) -> T
is the length or magnitude of a vectora
-
length2(vec<T,M> a) -> T
is the square of the length or magnitude of vectora
-
normalize(vec<T,M> a) -> vec<T,M>
is a unit length vector in the same direction asa
(undefined for zero-length vectors) -
distance(vec<T,M> a, vec<T,M> b) -> T
is the Euclidean distance between pointsa
andb
-
distance2(vec<T,M> a, vec<T,M> b) -> T
is the square of the Euclidean distance between pointsa
andb
-
angle(vec<T,M> a, vec<T,M> b) -> T
is the angle in radians between vectorsa
andb
-
uangle(vec<T,M> a, vec<T,M> b) -> T
is the angle in radians between unit vectorsa
andb
(undefined for non-unit vectors) -
rot(T a, vec<T,2> v) -> vec<T,2>
is the vectorv
rotated counter-clockwise by the anglea
in radians -
nlerp(vec<T,M> a, vec<T,M> b, T t) -> vec<T,M>
is shorthand fornormalize(lerp(a,b,t))
-
slerp(vec<T,M> a, vec<T,M> b, T t) -> vec<T,M>
is the spherical linear interpolation between unit vectorsa
andb
(undefined for non-unit vectors) by parametert
A small set of functions provides support for quaternion math, using vec<T,4>
values to represent quaternions of the form xi + yj + zk + w
.
-
qmul(vec<T,4> a, vec<T,4> b) -> vec<T,4>
is the Hamilton product of quaternionsa
andb
-
qconj(vec<T,4> q) -> vec<T,4>
is the conjugate of quaternionq
-
qinv(vec<T,4> q) -> vec<T,4>
is the inverse or reciprocal of quaternionq
(undefined for zero-length quaternions) -
qexp(vec<T,4> q) -> vec<T,4>
is the exponential of quaternionq
-
qlog(vec<T,4> q) -> vec<T,4>
is the logarithm of quaternionq
-
qpow(vec<T,4> q T p) -> vec<T,4>
is the quaternionq
raised to the exponentp
A second set of functions provides support for using unit-length quaternions to represent 3D spatial rotations. Their results are undefined for quaternions which are not of unit-length.
-
qangle(vec<T,4> q)
is the angle in radians of the rotation expressed by quaternionq
-
qaxis(vec<T,4> q)
is the axis of rotation expression by quaternionq
(undefined for zero-angle quaternions) -
qrot(vec<T,4> q, vec<T,3> v) -> vec<T,3>
is vectorv
rotated via rotation quaternionq
-
qmat(vec<T,4> q)
is a 3x3 rotation matrix which performs the same operation as rotation quaternionq
-
qxdir(vec<T,4> q)
is (efficient) shorthand forqrot(q, {1,0,0})
-
qydir(vec<T,4> q)
is (efficient) shorthand forqrot(q, {0,1,0})
-
qzdir(vec<T,4> q)
is (efficient) shorthand forqrot(q, {0,0,1})
It is possible to use the nlerp
and slerp
functions to interpolate rotation quaternions as though they were simply four-dimensional vectors. However, the rotation quaternions form a double cover over spatial rotations in three dimensions. This means that there are two distinct rotation quaternions representing each spatial rotation. Naively interpolating between two spatial rotations using quaternions could follow the "short path" or the "long path" between these rotations, depending on which specific quaternions are being interpolated.
qnlerp(vec<T,4> a, vec<T,4> b, T t)
is similar tonlerp(a,b,t)
, but always chooses the "short path" between the rotations represented bya
andb
.qslerp(vec<T,4> a, vec<T,4> b, T t)
is similar toslerp(a,b,t)
, but always chooses the "short path" between the rotations represented bya
andb
.
-
mul(mat<T,M,N> a, mat<T,N,P> b) -> mat<T,M,P>
is the matrix product of matricesa
andb
**mul(mat<T,M,N> a, vec<T,N> b) -> vec<T,M>
is the matrix product of matrixa
and a column matrix containing the elements of vectorb
**mul(a, b, c)
is shorthand formul(mul(a, b), c)
-
outerprod(vec<T,M> a, vec<T,N> b) -> mat<T,M,N>
is the outer product of vectorsa
andb
-
diagonal(mat<T,N,N> a) -> vec<T,N>
is a vector containing the elements along the main diagonal of matrixa
-
trace(mat<T,N,N> a) -> T
is the sum of the elements along the main diagonal of matrixa
-
transpose(mat<T,M,N> a) -> mat<T,N,M>
is the transpose of matrixa
-
adjugate(mat<T,N,N> a) -> mat<T,N,N>
is the adjugate or classical adjoint of matrixa
(the transpose of its cofactor matrix, or the numerator in the expression of its inverse) -
comatrix(mat<T,N,N> a) -> mat<T,N,N>
is the comatrix or cofactor matrix of matrixa
(the transpose of its adjugate matrix) -
determinant(mat<T,N,N> a) -> T
is the determinant of matrixa
-
inverse(mat<T,N,N> a) -> mat<T,N,N>
is the multiplicative inverse of the invertible matrixa
(undefined for singular inputs)
The unary functions abs
, floor
, ceil
, exp
, log
, log10
, sqrt
, sin
, cos
, tan
, asin
, acos
, atan
, sinh
, cosh
, tanh
, round
accept a vector-valued argument and produce a vector-valued result by passing individual elements to the function of the same name in the std::
namespace, as defined by <cmath>
or <cstdlib>
.
float4 a {1,-4,9,-16}; // a contains 1,-4,9,-16
float4 b = abs(a); // b contains 1,4,9,16
float4 c = sqrt(b); // c contains 1,2,3,4
The binary functions fmod
, pow
, atan2
, and copysign
function similarly, except that either argument can be a vector or a scalar.
float2 a {5,4}, b {2,3};
float2 c = pow(a, 2); // c contains 25,16
float2 d = pow(2, b); // d contains 4,8
float2 e = pow(a, b); // e contains 25,64
The binary functions equal
, nequal
, less
, greater
, lequal
, and gequal
apply operators ==
, !=
, <
, >
, <=
and >=
respectively in a component-wise fashion, returning a vec<bool,M>
. As before, either argument can be a vector or a scalar.
int2 a {2,5}, b {3,4};
bool2 c = less(a,3); // c contains true, false
bool2 d = equal(4,b); // d contains false, true
bool2 e = greater(a,b); // e contains false, true
min(a,b) -> vec<T,M>
performs the component-wise selection of lesser elements, as by a[i] < b[i] ? a[i] : b[i]
. Either argument can be a vector or a scalar.
max(a,b) -> vec<T,M>
performs the component-wise selection of greater elements, as by a[i] > b[i] ? a[i] : b[i]
. Either argument can be a vector or a scalar.
clamp(x,l,h) -> vec<T,M>
performs the component-wise clamping of elements between a low and high boundary, as by min(max(x,l),h)
. Any argument can be a vector or a scalar.
select(p,a,b) -> vec<T,M>
performs a component-wise ternary operator, as by p[i] ? a[i] : b[i]
. Any argument can be a vector or a scalar.
lerp(a,b,t) -> vec<T,M>
performs a component-wise linear interpolation, as by a[i]*(1-t[i]) + b[i]*t[i]
. Any argument can be a vector or a scalar.
any(vec<bool,M> a) -> bool
istrue
if any element of the vectora
istrue
all(vec<bool,M> a) -> bool
istrue
if all elements of the vectora
aretrue
sum(vec<T,M> a) -> T
is the sum of all elements in the vectora
product(vec<T,M> a) -> T
returns the product of all elements in the vectora
minelem(vec<T,M> a) -> T
returns the value of the least element in the vectora
maxelem(vec<T,M> a) -> T
returns the value of the greatest element in the vectora
argmin(vec<T,M> a) -> int
returns the zero-based index of the least element in the vectora
argmax(vec<T,M> a) -> int
returns the zero-based index of the greatest element in the vectora
compare(a,b)
is conceptually equivalent to operator <=>
from C++20. It compares two values of equivalent shape and returns a value which supports all six standard comparisons against 0
. It provides the same ordering guarantees as the underlying scalar type. That is, a vec<int,M>
provides a strong ordering, where a vec<float,M>
provides a partial odering.
By default, linalg.h
does not define any symbols in the global namespace, and a three-element vector of single-precision floating point values must be spelled linalg::vec<float,3>
. In various libraries and shading languages, such a type might be spelled float3
, vec3
, vec3f
, point3f
, simd_float3
, or any one of a hundred other possibilities. linalg.h
provides a collection of useful aliases in the linalg::aliases
namespace. If the names specified in this namespace are suitable for a user's purposes, they can quickly be brought into scope as follows:
#include <linalg.h>
using namespace linalg::aliases;
float3 a_vector;
float4x4 a_matrix;
Note that this only brings the type aliases into global scope. The core types and all functions and operator overloads defined by the library remain in namespace linalg
.
If the spellings in namespace linalg::aliases
conflict with other types that have been defined in the global namespace or in other namespaces of interest, the user can choose to omit the using namespace
directive and instead define their own aliases as desired.
#include <linalg.h>
using v3f = linalg::vec<float,3>;
using m44f = linalg::mat<float,4,4>;
v3f a_vector;
m44f a_matrix;
It is, of course, always possible to use the core linalg.h
types directly if operating in an environment where no additional symbols should be defined.
#include <linalg.h>
linalg::vec<float,3> a_vector;
linalg::mat<float,4,4> a_matrix;
The set of type aliases defined in namespace linalg::aliases
is as follows:
vec<float,M>
aliased to floatM, as in:float1
,float2
,float3
,float4
vec<double,M>
aliased to doubleM, as in:double1
,double2
,double3
,double4
vec<int,M>
aliased to intM as in:int1
,int2
,int3
,int4
vec<unsigned,M>
aliased to uintM as in:uint1
,uint2
,uint3
,uint4
vec<bool,M>
aliased to boolM as in:bool1
,bool2
,bool3
,bool4
vec<int16_t,M>
aliased to shortM as in:short1
,short2
,short3
,short4
vec<uint16_t,M>
aliased to ushortM as in:ushort1
,ushort2
,ushort3
,ushort4
vec<uint8_t,M>
aliased to byteM as in:byte1
,byte2
,byte3
,byte4
mat<float,M,N>
aliased to floatMxN as in:float1x3
,float3x2
,float4x4
, etc.mat<double,M,N>
aliased to doubleMxN as in:double1x3
,double3x2
,double4x4
, etc.mat<int,M,N>
aliased to intMxN as in:int1x3
,int3x2
,int4x4
, etc.mat<bool,M,N>
aliased to boolMxN as in:boolx3
,bool3x2
,bool4x4
, etc.
All combinations of up to four elements, rows, or columns are provided.
By default, linalg.h
does not provide operators for interaction with standard library streams. This is to permit maximum flexibility for users who wish to define their own formatting (with or without delimiters, row versus column major matrices, human-readable precision or round-trip exact). However, as it is often useful to simply be able to show something when writing small programs, we provide some default stream operator overloads which can be brought into scope with:
#include "linalg.h"
using namespace linalg::ostream_overloads;
The provided behavior is to output a string using the currently specified stream properties (width, precision, padding, etc) which matches the braced-initialization syntax that could be used to construct that same value, without any extra whitespace.
int3 v {1, 2, 3};
int2x2 m {{4, 5}, {6, 7}};
std::cout << v << std::endl; // Prints {1,2,3}
std::wcout << m << std::endl; // Prints {{4,5},{6,7}}
A mechanism exists to define automatic conversions between linalg
and user-provided types. As an example, this mechanism has already been used to defined bidirectional conversions between linalg::vec<T,M>
and std::array<T,M>
.
TODO: Explain converter<T,U>
fold(f, a, b)
is a higher order function which accepts a function of the form A,B => A
and repeatedly invokes a = f(a, element_of_b)
until all elements have been consumed, before returning a
. It is approximately equal to a left fold with an initial value. When b
is a vec<T,M>
, elements are folded from least to greatest index. When b
is a mat<T,M,N>
, elements are folded in column-major order.
See also: Reductions
apply(f, a...)
is a higher order function which accepts a function of the form A... => T
and applies it to component-wise sets of elements from data structures of compatible shape and dimensions. It is approximately equal to a convolution followed by a map. The shape of the result (that is, whether it is a scalar, vector, or matrix, and the dimensions thereof) is determined by the arguments. If more than one argument is a non-scalar, the shape of those arguments must agree. Scalars can be freely intermixed with non-scalars, and element types can also be freely mixed. The element type of the returned value is determined by the return type of the provided mapping function f
. The supported call signatures are enumerated in the following table:
call | type of a |
type of b |
type of c |
result type | result elements |
---|---|---|---|---|---|
apply(f,a) |
A |
T |
f(a) |
||
apply(f,a) |
vec<A,M> |
vec<T,M> |
f(a[i])... |
||
apply(f,a) |
mat<A,M,N> |
mat<T,M,N> |
f(a[j][i])... |
||
apply(f,a,b) |
A |
B |
T |
f(a, b)... |
|
apply(f,a,b) |
A |
vec<B,M> |
vec<T,M> |
f(a, b[i])... |
|
apply(f,a,b) |
vec<A,M> |
B |
vec<T,M> |
f(a[i], b)... |
|
apply(f,a,b) |
vec<A,M> |
vec<B,M> |
vec<T,M> |
f(a[i], b[i])... |
|
apply(f,a,b) |
A |
mat<B,M,N> |
mat<T,M,N> |
f(a, b[j][i])... |
|
apply(f,a,b) |
mat<A,M,N> |
B |
mat<T,M,N> |
f(a[j][i], b)... |
|
apply(f,a,b) |
mat<A,M,N> |
mat<B,M,N> |
mat<T,M,N> |
f(a[j][i], b[j][i])... |
|
apply(f,a,b,c) |
A |
B |
C |
T |
f(a, b, c)... |
apply(f,a,b,c) |
A |
B |
vec<C,M> |
vec<T,M> |
f(a, b, c[i])... |
apply(f,a,b,c) |
A |
vec<B,M> |
C |
vec<T,M> |
f(a, b[i], c)... |
apply(f,a,b,c) |
A |
vec<B,M> |
vec<C,M> |
vec<T,M> |
f(a, b[i], c[i])... |
apply(f,a,b,c) |
vec<A,M> |
B |
C |
vec<T,M> |
f(a[i], b, c)... |
apply(f,a,b,c) |
vec<A,M> |
B |
vec<C,M> |
vec<T,M> |
f(a[i], b, c[i])... |
apply(f,a,b,c) |
vec<A,M> |
vec<B,M> |
C |
vec<T,M> |
f(a[i], b[i], c)... |
apply(f,a,b,c) |
vec<A,M> |
vec<B,M> |
vec<C,M> |
vec<T,M> |
f(a[i], b[i], c[i])... |
TODO: Explain apply_t<F, A...>
and SFINAE helpers.
See also: Component-wise operations
map(a,f)
andzip(a,b,f)
subsumed by newapply(f,a...)
apply(...)
supports unary, binary, and ternary operations forvec
apply(...)
supports unary and binary operations format
andquat
apply(...)
can also be invoked exclusively with scalars, and supports arbitrary numbers of argumentsapply(...)
supports mixed element types- Template type alias
apply_t<F,A...>
provides the return type ofapply(f,a...)
vec<T,1>
andmat<T,M,1>
specializations are now providedcompare(a,b)
provide three-way comparison between compatible typesclamp(a,b,c)
can be invoked with three distinct (but compatible) typesselect(a,b,c)
provides the a component-wise equivalent toa ? b : c
lerp(a,b,t)
has been generalized to a component-wise operation where any ofa
,b
, andt
can be vectors or scalars- User can specialize
converter<T,U>
to enable implicit conversions fromU
toT
, if either type is avec
,mat
, orquat
identity
is implemented using this facility to serve as an in-library example
- No undefined behavior according to the C++11 standard
- Almost all operations which do not internally call
<cmath>
functions areconstexpr
, except forargmin
andargmax
- No lambdas are used in
linalg.h
, avoiding potential ODR violations
operator *
has been deprecated between pairs of matrices.- Call
cmul(...)
if the original, component-wise product was intended - Call
mul(...)
if the algebraic matrix product was intended
- Call
You can #define LINALG_FORWARD_COMPATIBLE
before including linalg.h
to remove all deprecated features.
It is intended that compatibility will be restored before officially tagging v2.2
linalg.h
no longer supports Visual Studio 2013. However, it is known to work on GCC 4.9+, Clang 3.5+ in C++11 mode and Visual Studio 2015+.vec<T,M>
andmat<T,M,N>
may only be used with aT
which is an arithmetic type- This requirement will likely be relaxed, but will require specializing some trait type to indicate additional scalar types