MGL design is around types and operators.
Operators have defined behavior for specific prototypes (a list of parameter types: MGL/special types).
Any operator prototype can be (re)defined.
|
When an operator prototype is (re)defined, it will (re)generate the operator function. Previously referenced operators will still work, but without the updated behavior. listenOps can be used to update those references.
|
ℹ️
|
Operators will lazily check for expected parameters, additional arguments will not trigger an error if a prototype matches. |
MGL types can be Lua types (number, boolean, etc.) or table-based types from the metatable pool.
All types are recognized by a string: the name from the metatable pool or the result of the Lua type
function.
ℹ️
|
The references from the metatable pool should normally be constants, but the content of the metatable can be modified. |
__tostring |
tostring |
__unm |
unm |
__add |
add |
__sub |
sub |
__mul |
mul |
__div |
div |
__mod |
mod |
__pow |
pow |
__eq |
equal |
__lt |
lessThan |
__le |
lessEqual |
Types can have specialized metamethods; for example, to implement accessors.
ℹ️
|
Accessors are implemented as simple as possible, they are check free. |
-- Loading the module will return (mgl, mglt).
-- MGL
-- Operator prototypes.
-- Registered functions taking parameters of specific types.
-- Map of operator id => nested tables. Each level contains parameter types as key
-- with t[1] as the final function and t[2] as the op table index.
mgl.ops
-- MGL types metatable pool.
-- Accessing a nil field will create the type.
-- map of MGL type => metatable
mgl.types
-- return MGL type (table-based type name or Lua type)
mgl.type(v)
-- Define operator prototype function.
-- It will replace the previous prototype function if identical.
-- Calling this function will mark the operator for update (old references will
-- work, but without the updated behavior).
--
-- func(...): called with operands
-- ...: strings, operator id and prototype (parameter types)
--- parameter types: MGL types or special types ("*": any non-nil)
mgl.defOp(func, ...)
-- Get operator prototype function.
-- ...: strings, operator id and prototype (parameter types)
-- return function or falsy if not found / invalid
mgl.getOp(...)
-- Listen to operator definitions.
-- callback(mgl, op): called when an operator definition changes (prototype update)
--- mgl: MGL handle
--- op: operator id
mgl.listenOps(callback)
-- Unlisten from operator definitions.
-- callback: previously registered callback
mgl.unlistenOps(callback)
-- Operators.
-- mgl.<...>
-- MGL tools
-- Generate function.
-- name: identify the generated function for debug
mglt.genfunc(code, name)
-- Generate "a1, a2, a3, a4..." list string.
-- t_element: string where "$" will be replaced by the element index
-- a: start index
-- b: end index
-- separator: (optional) default: ", "
mglt.genlist(t_element, a, b, separator)
-- Template substitution.
-- template: string with $... parameters
-- args: map of param => value
-- return processed template
mglt.tplsub(template, args)
-- Format prototype for debug.
-- id: operator id
-- ...: parameter types
mglt.format_proto(id, ...)
-- Format call prototype for debug.
-- id: operator id
-- ...: arguments
mglt.format_call(id, ...)
Generic vector type of dimension D
, stored as an array/list of scalars (table).
-- Generate vec(D) vector type.
-- D: dimension
mgl.gen_vec(D)
-- Accessors.
-- vec.x / vec.r (vec[1])
-- vec.y / vec.g (vec[2])
-- vec.z / vec.b (vec[3])
-- vec.w / vec.a (vec[4])
#vec -- dimension
Generic matrix type of dimension N x M
, stored as an array/list of row-major ordered scalars (table). Columns are vectors.
ℹ️
|
The choice of the row-major order is about reading/writing a matrix content as we read/write text/code in English/Lua (left to right, top to bottom). The choice of columns as vectors is about following mathematical conventions ( M*v to transform a vector).
|
-- Generate mat(N)(M)/mat(N) vector type.
-- N: columns
-- M: (optional) rows (default: N)
mgl.gen_mat(N, M)
-- Vector accessor (get/set column vector).
-- idx: column index
-- vec: (optional) vec(M), set column
mat:v(idx, vec)
mat.M, mat.N -- dimensions
Vector constructor.
(number: scalars…): vec(D)
-
Scalars constructor.
#scalars… == D
(number: scalar): vec(D)
-
Scalar constructor.
(table: list): vec(D)
-
List constructor.
#list >= D
(vec(D): a): vec(D)
-
Copy constructor.
(vec(D+1): a): vec(D)
-
Truncate constructor.
(vec(A): a, vec(B) or scalar: b): vec(D)
-
Composed constructor.
Vector and vector/scalar:1 < A < D
andB <= A
.Examples-
vec3(vec2(1), 1)
-
vec4(vec2(1), vec2(1))
-
vec4(vec3(1), 1)
-
Matrix constructor.
(number: scalar): mat(N)x(M)
-
Scalar constructor. Create matrix with
scalar
along the identity diagonal. (table: list): mat(N)x(M)
-
List constructor.
#list >= N*M
(mat(N)x(M): a): mat(N)x(M)
-
Copy constructor.
(vec(M): columns…): mat(N)x(M)
-
Column vectors constructor.
#columns… == N
(mat(N-1): a): mat(N)
-
Square extend constructor. Fill with identity.
(mat(N+1): a): mat(N)
-
Square truncate constructor.
(vec(D): a, vec(D): b): vec(D)
-
Component-wise multiplication.
(vec(D): a, number: b): vec(D)
-
-
(number: a, vec(D): b): vec(D)
-
-
(mat(N): a, mat(N): b): mat(N)
-
Square matrix multiplication.
(mat(N): a, vec(N): b): vec(N)
-
Square matrix/vector multiplication.
(mat(N)x(M): a, mat(O)x(N) or vec(N): b): mat(O)x(M) or vec(M)
-
Matrix/vector general multiplication (implemented with the special type
*
for the second parameter).
Will return a vector if the result has a single column. (mat(N)x(M): a, number: b): mat(N)x(M)
-
-
(number: a, mat(N)x(M): b): mat(N)x(M)
-
-
(vec(D): a, vec(D): b): vec(D)
-
Component-wise division.
(vec(D): a, number: b): vec(D)
-
-
(mat(N)x(M): a, number: b): mat(N)x(M)
-
-
(mat2: a): mat2, number
-
Compute inverse matrix. Also returns determinant.
(mat3: a): mat3, number
-
Compute inverse matrix. Also returns determinant.
(mat4: a): mat4, number
-
Compute inverse matrix. Also returns determinant.
(vec2: a): mat3
-
Translate identity (2D homogeneous).
(vec3: a): mat4
-
Translate identity (3D homogeneous).
(number: theta): mat3
-
Rotate identity (2D homogeneous).
theta
is in radians. (vec3: axis, number: theta): mat4
-
Rotate identity (3D homogeneous).
axis
is a unit vector;theta
is in radians.
(vec2: a): mat3
-
Scale identity (2D homogeneous).
(vec3: a): mat4
-
Scale identity (3D homogeneous).
Orthographic projection.
(number: left, number: right, number: bottom, number: top, number: near, number: far): mat4
-
Build GL compatible orthographic projection.
-
Operators branch to operator prototypes with a generated type-checking function.
-
More an operator has prototypes, more it has type-checking code: best to only generate required types.
-
This has an overhead, but it is probably less significant with the LuaJIT interpreter than PUC Lua.
-
Furthermore, the LuaJIT compiler may eliminate all of the overhead.
-
In any case, the operator prototype can be retrieved and cached with
getOp
when optimizations are needed.
Here are some comparisons with other libraries (only aims to give clues about MGL performances).
Measures are made on a x86_64 i5-6500 3.6GHz 8Go DDR4
machine.
The minimum time and the maximum memory (RSS) of multiple measures is kept.
For total times, allocation of entities is taken into account.
name | time (s) | CPU utime (s) | memory (kB) | ~ ms/tick | ~ frame % | code |
---|---|---|---|---|---|---|
GLM GCC -O2 |
0.366 |
0.362 |
4040 |
0.597 |
4 |
|
MGL LuaJIT (JIT on) |
2.477 |
2.371 |
19000 |
4.112 |
25 |
|
CPML LuaJIT (JIT on) |
4.224 |
4.205 |
8984 |
7.048 |
43 |
MGL is around 6-7x slower than GLM in this benchmark. It seems fine considering that MGL works on raw tables with a straightforward API (thanks to LuaJIT optimizations like Allocation Sinking).