Mathematics for Graphics in Lua, or Mathematics for OpenGL, is a math library for graphics purposes. It is effectively designed for 2D/3D rendering (but may also be used for physics).
It aims to be simple and optimized, mostly for LuaJIT.
It tries to follow GLSL, but it will diverge when relevant. One benefit is the decrease in mental load when working with Lua (CPU) and GLSL (GPU), as it is the case with LÖVE.
Table of Content
Install
See src/
, rockspecs/
or
luarocks.
Quick downloads: trunk
API
local mgl = require "mgl"
Generalities
Conventions
- Matrix layout is in column-major order.
- A vector is a column (column-vector).
Noteworthy Differences With GLSL
- 1-based indices.
- Using the same component twice to perform a swizzling write is not forbidden.
- Combination of different swizzle mask sets is not forbidden.
- No
stpq
swizzle mask set. - Matrix columns cannot be directly accessed as vectors. I.e.
m[1]
is not the first column and is not a vector.
Vector
vecM
is a column vector of size M, i.e. with M rows.
A vector is a table sequence of the components.
The following vector types are available: vec2, vec3, vec4.
Constructors
Scalar
mgl.vecM (s: number) -> vecM
Construct from a scalar. All components are set to the value s
.
Table
mgl.vecM (t: table) -> vecM
Construct a vector from a raw table of the same length. The table content is copied.
Sequence
mgl.vecM (...) -> vecM
Construct from a sequence of scalars and vectors matching the number of components.
Examples:
mgl.vec3(mgl.vec2(1, 2), 3)
mgl.vec4(mgl.vec2(0), mgl.vec2(1))
mgl.vec4(mgl.vec4(1))
Truncate
mgl.vecM (v: vecM+) -> vecM
Construct a vector from one with more components.
Accessors
Table Access
As a table sequence, each component can be accessed with its index.
E.g. v[1], v[2], v[3]
.
Swizzling
See GLSL.
Defined masks are xyzw
and rgba
.
Examples:
local v2 = mgl.vec2(1)
local v4 = mgl.vec4(v2.xxyy)
local v4_mirror = v4.wzyx
local c1 = mgl.vec4(1, 0, 0, 0.5)
local c2 = mgl.vec4(0, 1, 0, 1)
local c3 = mgl.vec4(c1.rgb, c2.a)
Operators
Arithmetic Operators
Defined operators are: add +
, sub -
, unm -
, mul *
, div /
, mod %
, pow
^
.
They all work component-wise. Binary operators work with another vector of the same size or with a scalar.
(a: vecM, b: vecM) -> vecM
(a: vecM, b: number) -> vecM
(a: number, b: vecM) -> vecM
Examples:
1 / mgl.vec2(2) -- (1/2, 1/2)
2 ^ mgl.vec3(1, 2, 3) -- (2, 4, 8)
mgl.vec3(1, 2, 3) % 2 -- (1, 0, 1)
Other Operators
The eq
relational operator and tostring
are defined.
Functions
length
mgl.length (x: vecM) -> number
Calculate the length of a vector.
normalize
mgl.normalize (x: vecM) -> vecM
Calculate the unit vector in the same direction as the original vector.
distance
mgl.distance (x: vecM, y: vecM) -> number
Calculate the distance between two points.
dot
mgl.dot (x: vecM, y: vecM) -> number
Calculate the dot product of two vectors.
cross
mgl.cross (x: vec3, y: vec3) -> vec3
Calculate the cross product of two vectors.
reflect
mgl.reflect (i: vecM, n: vecM) -> vecM
Calculate the reflection direction for an incident vector.
For a given incident vector
i
and surface normaln
, reflect returns the reflection direction calculated asi - 2.0 * dot(n, i) * n
.
n
should be normalized in order to achieve the desired result. — glsl
refract
mgl.refract (i: vecM, n: vecM, eta: number) -> vecM
Calculate the refraction direction for an incident vector.
i
: incident vectorn
: normal vectoreta
: ratio of indices of refraction
The input parameters
i
andn
should be normalized in order to achieve the desired result. — glsl
Matrix
matNxM
is a matrix of N columns and M rows.
A matrix is a table sequence of all the values in column-major order.
The following matrix types are available:
- mat2x2 mat3x3 mat4x4 (square matrices)
- mat2x3 mat3x2
- mat2x4 mat4x2
- mat3x4 mat4x3
In addition, matN
is an alias for the square matrix matNxN
(i.e. mat2, mat3
and mat4).
Constructors
Scalar
mgl.matNxM (s: number) -> matNxM
Construct from a scalar. The diagonal of the matrix is set to s
, the rest is
0
. If s == 1
, it builds the identity matrix.
Copy
mgl.matNxM (m: matrix) -> matNxM
Construct a matrix from any other matrix, of any size. Existing values are copied, the rest is filled with the identity matrix.
Table
mgl.matNxM (t: table) -> matNxM
Construct a matrix from a raw table with the right amount of values in column-major order. The table content is copied.
Sequence
mgl.matNxM (...) -> matNxM
Construct from a sequence of scalars and vectors matching the number of values in the matrix.
Because matrix values are stored in column-major order, it is possible to do:
local m = mgl.mat4(
mgl.vec4(1), -- first column
mgl.vec4(2), -- second column
mgl.vec4(3), -- third column
mgl.vec4(4)) -- fourth column
See GLSL.
Accessors
Table Access
As a table sequence, each value can be accessed with its index.
E.g. m[1], m[2], m[3]...
.
Scalar Access
matNxM:a (i: number, j: number) -> number
Method to get one value from the matrix. If the coordinates are outside the
matrix, it returns nil
.
i
: row 1-based indexj
: column 1-based index
matNxM:a (i: number, j: number, v: number)
Method to set one value of the matrix. If the coordinates are outside the matrix, it throws an error.
i
: row 1-based indexj
: column 1-based indexv
: value
Vector Access
matNxM:v (j: number) -> vecM
Method to get one vector from the matrix. If the index is invalid, it returns
nil
.
j
: column 1-based index
matNxM:v (j: number, v: vecM)
Method to set one vector of the matrix. If the index is invalid, it throws an error.
j
: column 1-based indexv
: vector
Operators
Arithmetic Operators
Defined operators are: add +
, sub -
, unm -
, mul *
, div /
, mod %
, pow
^
.
Except for multiplication, they all work component-wise. Binary operators work with another matrix of the same size or with a scalar.
(a: matNxM, b: matNxM) -> matNxM
(a: matNxM, b: number) -> matNxM
(a: number, b: matNxM) -> matNxM
For multiplication, it performs the matrix product1. The number of columns in the first matrix must be equal to the number of rows in the second matrix. In addition, a vector is considered a matrix with a single column.
(a: matNxM, b: matPxN) -> matPxM
(a: matNxM, b: vecN) -> vecN
Other Operators
The eq
relational operator and tostring
are defined.
Functions
transpose
mgl.transpose (m: matNxM) -> matMxN
Calculate the transpose of a matrix.
determinant
mgl.determinant (m: matN) -> number
Calculate the determinant of a matrix.
inverse
mgl.inverse (m: matN) -> matN
Calculate the inverse of a matrix.
Transform Functions
translation
mgl.translation (v: vec2) -> mat3
Build a 2D homogeneous translation matrix from a vector.
mgl.translation (v: vec3) -> mat4
Build a 3D homogeneous translation matrix from a vector.
rotation
mgl.rotation (theta: number) -> mat3
Build a 2D homogeneous rotation matrix from an angle in radians.
mgl.rotation (axis: vec3, theta: number) -> mat4
Build a 3D homogeneous rotation matrix from a unit vector and an angle in radians.
scale
mgl.scale (v: vec2) -> mat3
Build a 2D homogeneous scale matrix from a vector.
mgl.scale (v: vec3) -> mat4
Build a 3D homogeneous scale matrix from a vector.
Projection Functions
orthographic
mgl.orthographic (left, right, bottom, top, near, far) -> mat4
Build an orthographic projection matrix compatible with OpenGL. All parameters are numbers.
perspective
mgl.perspective (hfov, aspect, near, far) -> mat4
Build a perspective projection matrix compatible with OpenGL. All parameters are numbers.
hfov
: horizontal field of view (FOV) in radiansaspect
: aspect ratio (e.g.16/9
)near
,far
: planes
Scalar / Component-wise Functions
These functions operate on every component independently.
In the documentation of these functions, the type T
can be a scalar, a vector
or a matrix.
If there is no explanation for a function, it can be found in the GLSL specification at the function of the same name.
Single Argument
mgl.<func> (a: T) -> T
Apply a maths function to each component of a
.
Available functions:
- abs, sign
- ceil, floor, round, fract
- sqrt
- cos, acos, cosh
- sin, asin, sinh
- tan, atan, tanh
- deg, degrees
- rad, radians
- exp, log
- exp2, log2
Note: deg
and rad
are aliases for degrees
and radians
.
Two Arguments
mgl.<func> (a: T, b: T or number) -> T
Apply a maths function to each component of a
, using b
. Argument b
can be
of the same type as a
or a scalar.
Available functions:
- min, max
- cmul: Component-wise multiplication (useful for matrices).
Other Functions
clamp
mgl.clamp (x: T, a: T or number, b: T or number) -> T
Constrain a value to lie between two further values.
The returned value is computed as min(max(x, a), b)
.
mix
mgl.mix (x: T, y: T, a: T or number) -> T
Linearly interpolate between two values.
The returned value is computed as x * (1 − a) + y * a
. The multiplication is
component-wise using cmul
.
Performances
Benchmarks
Notes:
- Measures are made on a
x86_64 i5-6500 3.6GHz 16Go DDR4
machine. - 5 measures are made; the average and standard deviation are computed.
- Time is measured with
clock
(CPU time). - The comparison is not perfect (see the benchmarks code).
Time to transform 2500 entities at 60 FPS for 10s (600 ticks):
name | time | ms / tick | frame % | code |
---|---|---|---|---|
GLM GCC -O2 | 0.306 ±0.004 | 0.510 | 3 % | benchmarks/compare/glm/transform.cpp |
MGL LuaJIT (JIT on) | 0.382 ±0.009 | 0.637 | 4 % | benchmarks/transform.lua |
MGL LuaJIT (JIT off) | 7.723 ±0.184 | 12.872 | 77 % | benchmarks/transform.lua |
MGL Lua 5.4 | 15.425 ±0.168 | 25.708 | 154 % | benchmarks/transform.lua |
Observations:
- MGL works on raw tables with a straightforward API and still can have performances on par (~25% more time) with GLM, thanks to LuaJIT optimizations, especially Allocation Sinking.
- In this example, the performances may decrease by 20x if the trace is not compiled. Edge cases where traces cannot be reliably compiled may be a problem.