• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excessive / cpml / 13079282556

07 May 2022 10:53PM UTC coverage: 57.717% (+43.7%) from 14.013%
13079282556

push

github

shakesoda
fix typo in mat4.mul

0 of 1 new or added line in 1 file covered. (0.0%)

527 existing lines in 19 files now uncovered.

4581 of 7937 relevant lines covered (57.72%)

52.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.52
/modules/mat4.lua
1
--- double 4x4, 1-based, column major matrices
2
-- @module mat4
3
local modules   = (...):gsub('%.[^%.]+$', '') .. "."
3✔
4
local constants = require(modules .. "constants")
3✔
5
local vec2      = require(modules .. "vec2")
3✔
6
local vec3      = require(modules .. "vec3")
3✔
7
local quat      = require(modules .. "quat")
3✔
8
local utils     = require(modules .. "utils")
3✔
9
local precond   = require(modules .. "_private_precond")
3✔
10
local private   = require(modules .. "_private_utils")
3✔
11
local sqrt      = math.sqrt
3✔
12
local cos       = math.cos
3✔
13
local sin       = math.sin
3✔
14
local tan       = math.tan
3✔
15
local rad       = math.rad
3✔
16
local mat4      = {}
3✔
17
local mat4_mt   = {}
3✔
18

19
-- Private constructor.
20
local function new(m)
21
        m = m or {
58✔
22
                0, 0, 0, 0,
55✔
23
                0, 0, 0, 0,
55✔
24
                0, 0, 0, 0,
55✔
25
                0, 0, 0, 0
55✔
26
        }
55✔
27
        m._m = m
58✔
28
        return setmetatable(m, mat4_mt)
58✔
29
end
30

31
-- Convert matrix into identity
32
local function identity(m)
33
        m[1],  m[2],  m[3],  m[4]  = 1, 0, 0, 0
11✔
34
        m[5],  m[6],  m[7],  m[8]  = 0, 1, 0, 0
11✔
35
        m[9],  m[10], m[11], m[12] = 0, 0, 1, 0
11✔
36
        m[13], m[14], m[15], m[16] = 0, 0, 0, 1
11✔
37
        return m
11✔
38
end
39

40
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
41
local status, ffi
42
if type(jit) == "table" and jit.status() then
3✔
43
        --  status, ffi = pcall(require, "ffi")
44
        if status then
×
45
                ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;"
×
UNCOV
46
                new = ffi.typeof("cpml_mat4")
×
47
        end
48
end
49

50
-- Statically allocate a temporary variable used in some of our functions.
51
local tmp = new()
3✔
52
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
3✔
53
local tv4 = { 0, 0, 0, 0 }
3✔
54

55
--- The public constructor.
56
-- @param a Can be of four types: </br>
57
-- table Length 16 (4x4 matrix)
58
-- table Length 9 (3x3 matrix)
59
-- table Length 4 (4 vec4s)
60
-- nil
61
-- @treturn mat4 out
62
function mat4.new(a)
3✔
63
        local out = new()
39✔
64

65
        -- 4x4 matrix
66
        if type(a) == "table" and #a == 16 then
39✔
67
                for i = 1, 16 do
170✔
68
                        out[i] = tonumber(a[i])
160✔
69
                end
70

71
        -- 3x3 matrix
72
        elseif type(a) == "table" and #a == 9 then
29✔
73
                out[1], out[2],  out[3]  = a[1], a[2], a[3]
1✔
74
                out[5], out[6],  out[7]  = a[4], a[5], a[6]
1✔
75
                out[9], out[10], out[11] = a[7], a[8], a[9]
1✔
76
                out[16] = 1
1✔
77

78
        -- 4 vec4s
79
        elseif type(a) == "table" and type(a[1]) == "table" then
28✔
80
                local idx = 1
1✔
81
                for i = 1, 4 do
5✔
82
                        for j = 1, 4 do
20✔
83
                                out[idx] = a[i][j]
16✔
84
                                idx = idx + 1
16✔
85
                        end
86
                end
87

88
        -- nil
89
        else
90
                out[1]  = 1
27✔
91
                out[6]  = 1
27✔
92
                out[11] = 1
27✔
93
                out[16] = 1
27✔
94
        end
95

96
        return out
39✔
97
end
98

99
--- Create an identity matrix.
100
-- @tparam mat4 a Matrix to overwrite
101
-- @treturn mat4 out
102
function mat4.identity(a)
3✔
103
        return identity(a or new())
1✔
104
end
105

106
--- Create a matrix from an angle/axis pair.
107
-- @tparam number angle Angle of rotation
108
-- @tparam vec3 axis Axis of rotation
109
-- @treturn mat4 out
110
function mat4.from_angle_axis(angle, axis)
3✔
111
        local l = axis:len()
×
112
        if l == 0 then
×
UNCOV
113
                return new()
×
114
        end
115

116
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
×
117
        local c = cos(angle)
×
UNCOV
118
        local s = sin(angle)
×
119

120
        return new {
×
121
                x*x*(1-c)+c,   y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
×
122
                x*y*(1-c)-z*s, y*y*(1-c)+c,   y*z*(1-c)+x*s, 0,
×
123
                x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c,   0,
×
UNCOV
124
                0, 0, 0, 1
×
125
        }
126
end
127

128
--- Create a matrix from a quaternion.
129
-- @tparam quat q Rotation quaternion
130
-- @treturn mat4 out
131
function mat4.from_quaternion(q)
3✔
UNCOV
132
        return mat4.from_angle_axis(q:to_angle_axis())
×
133
end
134

135
--- Create a matrix from a direction/up pair.
136
-- @tparam vec3 direction Vector direction
137
-- @tparam vec3 up Up direction
138
-- @treturn mat4 out
139
function mat4.from_direction(direction, up)
3✔
140
        local forward = vec3.normalize(direction)
×
141
        local side = vec3.cross(forward, up):normalize()
×
UNCOV
142
        local new_up = vec3.cross(side, forward):normalize()
×
143

144
        local out = new()
×
145
        out[1]    = side.x
×
146
        out[5]    = side.y
×
147
        out[9]    = side.z
×
148
        out[2]    = new_up.x
×
149
        out[6]    = new_up.y
×
150
        out[10]   = new_up.z
×
151
        out[3]    = forward.x
×
152
        out[7]    = forward.y
×
153
        out[11]   = forward.z
×
UNCOV
154
        out[16]   = 1
×
155

UNCOV
156
        return out
×
157
end
158

159
--- Create a matrix from a transform.
160
-- @tparam vec3 trans Translation vector
161
-- @tparam quat rot Rotation quaternion
162
-- @tparam vec3 scale Scale vector
163
-- @treturn mat4 out
164
function mat4.from_transform(trans, rot, scale)
3✔
165
        local rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
1✔
166

167
        local sm = new {
2✔
168
                scale.x, 0,       0,       0,
1✔
169
                0,       scale.y, 0,       0,
1✔
170
                0,       0,       scale.z, 0,
1✔
171
                0,       0,       0,       1,
1✔
172
        }
1✔
173

174
        local rm = new {
2✔
175
                1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0,
1✔
176
                2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0,
1✔
177
                2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0,
1✔
178
                0, 0, 0, 1
1✔
179
        }
1✔
180

181
        local rsm = rm * sm
1✔
182

183
        rsm[13] = trans.x
1✔
184
        rsm[14] = trans.y
1✔
185
        rsm[15] = trans.z
1✔
186

187
        return rsm
1✔
188
end
189

190
--- Create matrix from orthogonal.
191
-- @tparam number left
192
-- @tparam number right
193
-- @tparam number top
194
-- @tparam number bottom
195
-- @tparam number near
196
-- @tparam number far
197
-- @treturn mat4 out
198
function mat4.from_ortho(left, right, top, bottom, near, far)
3✔
199
        local out = new()
×
200
        out[1]    =  2 / (right - left)
×
201
        out[6]    =  2 / (top - bottom)
×
202
        out[11]   = -2 / (far - near)
×
203
        out[13]   = -((right + left) / (right - left))
×
204
        out[14]   = -((top + bottom) / (top - bottom))
×
205
        out[15]   = -((far + near) / (far - near))
×
UNCOV
206
        out[16]   =  1
×
207

UNCOV
208
        return out
×
209
end
210

211
--- Create matrix from perspective.
212
-- @tparam number fovy Field of view
213
-- @tparam number aspect Aspect ratio
214
-- @tparam number near Near plane
215
-- @tparam number far Far plane
216
-- @treturn mat4 out
217
function mat4.from_perspective(fovy, aspect, near, far)
3✔
218
        assert(aspect ~= 0)
4✔
219
        assert(near   ~= far)
4✔
220

221
        local t   = tan(rad(fovy) / 2)
4✔
222
        local out = new()
4✔
223
        out[1]    =  1 / (t * aspect)
4✔
224
        out[6]    =  1 / t
4✔
225
        out[11]   = -(far + near) / (far - near)
4✔
226
        out[12]   = -1
4✔
227
        out[15]   = -(2 * far * near) / (far - near)
4✔
228
        out[16]   =  0
4✔
229

230
        return out
4✔
231
end
232

233
-- Adapted from the Oculus SDK.
234
--- Create matrix from HMD perspective.
235
-- @tparam number tanHalfFov Tangent of half of the field of view
236
-- @tparam number zNear Near plane
237
-- @tparam number zFar Far plane
238
-- @tparam boolean flipZ Z axis is flipped or not
239
-- @tparam boolean farAtInfinity Far plane is infinite or not
240
-- @treturn mat4 out
241
function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
3✔
242
        -- CPML is right-handed and intended for GL, so these don't need to be arguments.
243
        local rightHanded = true
1✔
244
        local isOpenGL    = true
1✔
245

246
        local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
247
                local x_scale  = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
1✔
248
                local x_offset =     (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
1✔
249
                local y_scale  = 2 / (tanHalfFov.UpTan   + tanHalfFov.DownTan )
1✔
250
                local y_offset =     (tanHalfFov.UpTan   - tanHalfFov.DownTan ) * y_scale * 0.5
1✔
251

252
                local result = {
1✔
253
                        Scale  = vec2(x_scale, y_scale),
1✔
254
                        Offset = vec2(x_offset, y_offset)
1✔
255
                }
256

257
                -- Hey - why is that Y.Offset negated?
258
                -- It's because a projection matrix transforms from world coords with Y=up,
259
                -- whereas this is from NDC which is Y=down.
260
                 return result
1✔
261
        end
262

263
        if not flipZ and farAtInfinity then
1✔
264
                print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
×
UNCOV
265
                farAtInfinity = false
×
266
        end
267

268
         -- A projection matrix is very like a scaling from NDC, so we can start with that.
269
        local scaleAndOffset  = CreateNDCScaleAndOffsetFromFov(tanHalfFov)
1✔
270
        local handednessScale = rightHanded and -1.0 or 1.0
1✔
271
        local projection      = new()
1✔
272

273
        -- Produces X result, mapping clip edges to [-w,+w]
274
        projection[1] = scaleAndOffset.Scale.x
1✔
275
        projection[2] = 0
1✔
276
        projection[3] = handednessScale * scaleAndOffset.Offset.x
1✔
277
        projection[4] = 0
1✔
278

279
        -- Produces Y result, mapping clip edges to [-w,+w]
280
        -- Hey - why is that YOffset negated?
281
        -- It's because a projection matrix transforms from world coords with Y=up,
282
        -- whereas this is derived from an NDC scaling, which is Y=down.
283
        projection[5] = 0
1✔
284
        projection[6] = scaleAndOffset.Scale.y
1✔
285
        projection[7] = handednessScale * -scaleAndOffset.Offset.y
1✔
286
        projection[8] = 0
1✔
287

288
        -- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
289
        -- We'll just use some defaults for now.
290
        projection[9]  = 0
1✔
291
        projection[10] = 0
1✔
292

293
        if farAtInfinity then
1✔
UNCOV
294
                if isOpenGL then
×
295
                        -- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
296
                        projection[11] = -handednessScale
×
UNCOV
297
                        projection[12] = 2.0 * zNear
×
298
                else
299
                        projection[11] = 0
×
UNCOV
300
                        projection[12] = zNear
×
301
                end
302
        else
303
                if isOpenGL then
1✔
304
                        -- Clip range is [-w,+w], so 0 is at the middle of the range.
305
                        projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
1✔
306
                        projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
1✔
307
                else
308
                        -- Clip range is [0,+w], so 0 is at the start of the range.
309
                        projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
×
UNCOV
310
                        projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
×
311
                end
312
        end
313

314
        -- Produces W result (= Z in)
315
        projection[13] = 0
1✔
316
        projection[14] = 0
1✔
317
        projection[15] = handednessScale
1✔
318
        projection[16] = 0
1✔
319

320
        return projection:transpose(projection)
1✔
321
end
322

323
--- Clone a matrix.
324
-- @tparam mat4 a Matrix to clone
325
-- @treturn mat4 out
326
function mat4.clone(a)
3✔
327
        return new(a)
1✔
328
end
329

330
function mul_internal(out, a, b)
3✔
331
        tm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]
19✔
332
        tm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]
19✔
333
        tm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]
19✔
334
        tm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]
19✔
335
        tm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]
19✔
336
        tm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]
19✔
337
        tm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]
19✔
338
        tm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]
19✔
339
        tm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]
19✔
340
        tm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
19✔
341
        tm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
19✔
342
        tm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
19✔
343
        tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]
19✔
344
        tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
19✔
345
        tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
19✔
346
        tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
19✔
347

348
        for i = 1, 16 do
323✔
349
                out[i] = tm4[i]
304✔
350
        end
351
end
352

353
--- Multiply N matrices.
354
-- @tparam mat4 out Matrix to store the result
355
-- @tparam mat4 or {mat4, ...} left hand operand(s)
356
-- @tparam mat4 right hand operand if a is not table
357
-- @treturn mat4 out multiplied matrix result
358
function mat4.mul(out, a, b)
3✔
359
        if mat4.is_mat4(a) then
19✔
360
                mul_internal(out, a, b)
18✔
361
                return out
18✔
362
        end
363
        if #a == 0 then
1✔
364
                identity(out)
×
365
        elseif #a == 1 then
1✔
366
                -- only one matrix, just copy
367
                for i = 1, 16 do
×
NEW
368
                        out[i] = a[1][i]
×
369
                end
370
        else
371
                local ma = a[1]
1✔
372
                local mb = a[2]
1✔
373
                for i = 2, #a do
2✔
374
                        mul_internal(out, ma, mb)
1✔
375
                        ma = out
1✔
376
                end
377
        end
378
        return out
1✔
379
end
380

381
--- Multiply a matrix and a vec3, with perspective division.
382
-- This function uses an implicit 1 for the fourth component.
383
-- @tparam vec3 out vec3 to store the result
384
-- @tparam mat4 a Left hand operand
385
-- @tparam vec3 b Right hand operand
386
-- @treturn vec3 out
387
function mat4.mul_vec3_perspective(out, a, b)
3✔
388
        local v4x = b.x * a[1] + b.y * a[5] + b.z * a[9]  + a[13]
7✔
389
        local v4y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14]
7✔
390
        local v4z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15]
7✔
391
        local v4w = b.x * a[4] + b.y * a[8] + b.z * a[12] + a[16]
7✔
392
        local inv_w = 0
7✔
393
        if v4w ~= 0 then
7✔
394
                inv_w = utils.sign(v4w) / v4w
7✔
395
        end
396
        out.x = v4x * inv_w
7✔
397
        out.y = v4y * inv_w
7✔
398
        out.z = v4z * inv_w
7✔
399
        return out
7✔
400
end
401

402
--- Multiply a matrix and a vec4.
403
-- @tparam table out table to store the result
404
-- @tparam mat4 a Left hand operand
405
-- @tparam table b Right hand operand
406
-- @treturn vec4 out
407
function mat4.mul_vec4(out, a, b)
3✔
408
        tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]
6✔
409
        tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
6✔
410
        tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
6✔
411
        tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
6✔
412

413
        for i = 1, 4 do
30✔
414
                out[i] = tv4[i]
24✔
415
        end
416

417
        return out
6✔
418
end
419

420
--- Invert a matrix.
421
-- @tparam mat4 out Matrix to store the result
422
-- @tparam mat4 a Matrix to invert
423
-- @treturn mat4 out
424
function mat4.invert(out, a)
3✔
425
        tm4[1]  =  a[6] * a[11] * a[16] - a[6] * a[12] * a[15] - a[10] * a[7] * a[16] + a[10] * a[8] * a[15] + a[14] * a[7] * a[12] - a[14] * a[8] * a[11]
3✔
426
        tm4[2]  = -a[2] * a[11] * a[16] + a[2] * a[12] * a[15] + a[10] * a[3] * a[16] - a[10] * a[4] * a[15] - a[14] * a[3] * a[12] + a[14] * a[4] * a[11]
3✔
427
        tm4[3]  =  a[2] * a[7]  * a[16] - a[2] * a[8]  * a[15] - a[6]  * a[3] * a[16] + a[6]  * a[4] * a[15] + a[14] * a[3] * a[8]  - a[14] * a[4] * a[7]
3✔
428
        tm4[4]  = -a[2] * a[7]  * a[12] + a[2] * a[8]  * a[11] + a[6]  * a[3] * a[12] - a[6]  * a[4] * a[11] - a[10] * a[3] * a[8]  + a[10] * a[4] * a[7]
3✔
429
        tm4[5]  = -a[5] * a[11] * a[16] + a[5] * a[12] * a[15] + a[9]  * a[7] * a[16] - a[9]  * a[8] * a[15] - a[13] * a[7] * a[12] + a[13] * a[8] * a[11]
3✔
430
        tm4[6]  =  a[1] * a[11] * a[16] - a[1] * a[12] * a[15] - a[9]  * a[3] * a[16] + a[9]  * a[4] * a[15] + a[13] * a[3] * a[12] - a[13] * a[4] * a[11]
3✔
431
        tm4[7]  = -a[1] * a[7]  * a[16] + a[1] * a[8]  * a[15] + a[5]  * a[3] * a[16] - a[5]  * a[4] * a[15] - a[13] * a[3] * a[8]  + a[13] * a[4] * a[7]
3✔
432
        tm4[8]  =  a[1] * a[7]  * a[12] - a[1] * a[8]  * a[11] - a[5]  * a[3] * a[12] + a[5]  * a[4] * a[11] + a[9]  * a[3] * a[8]  - a[9]  * a[4] * a[7]
3✔
433
        tm4[9]  =  a[5] * a[10] * a[16] - a[5] * a[12] * a[14] - a[9]  * a[6] * a[16] + a[9]  * a[8] * a[14] + a[13] * a[6] * a[12] - a[13] * a[8] * a[10]
3✔
434
        tm4[10] = -a[1] * a[10] * a[16] + a[1] * a[12] * a[14] + a[9]  * a[2] * a[16] - a[9]  * a[4] * a[14] - a[13] * a[2] * a[12] + a[13] * a[4] * a[10]
3✔
435
        tm4[11] =  a[1] * a[6]  * a[16] - a[1] * a[8]  * a[14] - a[5]  * a[2] * a[16] + a[5]  * a[4] * a[14] + a[13] * a[2] * a[8]  - a[13] * a[4] * a[6]
3✔
436
        tm4[12] = -a[1] * a[6]  * a[12] + a[1] * a[8]  * a[10] + a[5]  * a[2] * a[12] - a[5]  * a[4] * a[10] - a[9]  * a[2] * a[8]  + a[9]  * a[4] * a[6]
3✔
437
        tm4[13] = -a[5] * a[10] * a[15] + a[5] * a[11] * a[14] + a[9]  * a[6] * a[15] - a[9]  * a[7] * a[14] - a[13] * a[6] * a[11] + a[13] * a[7] * a[10]
3✔
438
        tm4[14] =  a[1] * a[10] * a[15] - a[1] * a[11] * a[14] - a[9]  * a[2] * a[15] + a[9]  * a[3] * a[14] + a[13] * a[2] * a[11] - a[13] * a[3] * a[10]
3✔
439
        tm4[15] = -a[1] * a[6]  * a[15] + a[1] * a[7]  * a[14] + a[5]  * a[2] * a[15] - a[5]  * a[3] * a[14] - a[13] * a[2] * a[7]  + a[13] * a[3] * a[6]
3✔
440
        tm4[16] =  a[1] * a[6]  * a[11] - a[1] * a[7]  * a[10] - a[5]  * a[2] * a[11] + a[5]  * a[3] * a[10] + a[9]  * a[2] * a[7]  - a[9]  * a[3] * a[6]
3✔
441

442
        local det = a[1] * tm4[1] + a[2] * tm4[5] + a[3] * tm4[9] + a[4] * tm4[13]
3✔
443

444
        if det == 0 then return a end
3✔
445

446
        det = 1 / det
3✔
447

448
        for i = 1, 16 do
51✔
449
                out[i] = tm4[i] * det
48✔
450
        end
451

452
        return out
3✔
453
end
454

455
--- Scale a matrix.
456
-- @tparam mat4 out Matrix to store the result
457
-- @tparam mat4 a Matrix to scale
458
-- @tparam vec3 s Scalar
459
-- @treturn mat4 out
460
function mat4.scale(out, a, s)
3✔
461
        identity(tmp)
1✔
462
        tmp[1]  = s.x
1✔
463
        tmp[6]  = s.y
1✔
464
        tmp[11] = s.z
1✔
465

466
        return out:mul(tmp, a)
1✔
467
end
468

469
--- Rotate a matrix.
470
-- @tparam mat4 out Matrix to store the result
471
-- @tparam mat4 a Matrix to rotate
472
-- @tparam number angle Angle to rotate by (in radians)
473
-- @tparam vec3 axis Axis to rotate on
474
-- @treturn mat4 out
475
function mat4.rotate(out, a, angle, axis)
3✔
476
        if type(angle) == "table" or type(angle) == "cdata" then
4✔
UNCOV
477
                angle, axis = angle:to_angle_axis()
×
478
        end
479

480
        local l = axis:len()
4✔
481

482
        if l == 0 then
4✔
UNCOV
483
                return a
×
484
        end
485

486
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
4✔
487
        local c = cos(angle)
4✔
488
        local s = sin(angle)
4✔
489

490
        identity(tmp)
4✔
491
        tmp[1]  = x * x * (1 - c) + c
4✔
492
        tmp[2]  = y * x * (1 - c) + z * s
4✔
493
        tmp[3]  = x * z * (1 - c) - y * s
4✔
494
        tmp[5]  = x * y * (1 - c) - z * s
4✔
495
        tmp[6]  = y * y * (1 - c) + c
4✔
496
        tmp[7]  = y * z * (1 - c) + x * s
4✔
497
        tmp[9]  = x * z * (1 - c) + y * s
4✔
498
        tmp[10] = y * z * (1 - c) - x * s
4✔
499
        tmp[11] = z * z * (1 - c) + c
4✔
500

501
        return out:mul(tmp, a)
4✔
502
end
503

504
--- Translate a matrix.
505
-- @tparam mat4 out Matrix to store the result
506
-- @tparam mat4 a Matrix to translate
507
-- @tparam vec3 t Translation vector
508
-- @treturn mat4 out
509
function mat4.translate(out, a, t)
3✔
510
        identity(tmp)
3✔
511
        tmp[13] = t.x
3✔
512
        tmp[14] = t.y
3✔
513
        tmp[15] = t.z
3✔
514

515
        return out:mul(tmp, a)
3✔
516
end
517

518
--- Shear a matrix.
519
-- @tparam mat4 out Matrix to store the result
520
-- @tparam mat4 a Matrix to translate
521
-- @tparam number yx
522
-- @tparam number zx
523
-- @tparam number xy
524
-- @tparam number zy
525
-- @tparam number xz
526
-- @tparam number yz
527
-- @treturn mat4 out
528
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
3✔
529
        identity(tmp)
1✔
530
        tmp[2]  = yx or 0
1✔
531
        tmp[3]  = zx or 0
1✔
532
        tmp[5]  = xy or 0
1✔
533
        tmp[7]  = zy or 0
1✔
534
        tmp[9]  = xz or 0
1✔
535
        tmp[10] = yz or 0
1✔
536

537
        return out:mul(tmp, a)
1✔
538
end
539

540
--- Reflect a matrix across a plane.
541
-- @tparam mat4 Matrix to store the result
542
-- @tparam a Matrix to reflect
543
-- @tparam vec3 position A point on the plane
544
-- @tparam vec3 normal The (normalized!) normal vector of the plane
545
function mat4.reflect(out, a, position, normal)
3✔
546
        local nx, ny, nz = normal:unpack()
1✔
547
        local d = -position:dot(normal)
1✔
548
        tmp[1] = 1 - 2 * nx ^ 2
1✔
549
        tmp[2] = 2 * nx * ny
1✔
550
        tmp[3] = -2 * nx * nz
1✔
551
        tmp[4] = 0
1✔
552
        tmp[5] = -2 * nx * ny
1✔
553
        tmp[6] = 1 - 2 * ny ^ 2
1✔
554
        tmp[7] = -2 * ny * nz
1✔
555
        tmp[8] = 0
1✔
556
        tmp[9] = -2 * nx * nz
1✔
557
        tmp[10] = -2 * ny * nz
1✔
558
        tmp[11] = 1 - 2 * nz ^ 2
1✔
559
        tmp[12] = 0
1✔
560
        tmp[13] = -2 * nx * d
1✔
561
        tmp[14] = -2 * ny * d
1✔
562
        tmp[15] = -2 * nz * d
1✔
563
        tmp[16] = 1
1✔
564

565
        return out:mul(tmp, a)
1✔
566
end
567

568
--- Transform matrix to look at a point.
569
-- @tparam mat4 out Matrix to store result
570
-- @tparam vec3 eye Location of viewer's view plane
571
-- @tparam vec3 center Location of object to view
572
-- @tparam vec3 up Up direction
573
-- @treturn mat4 out
574
function mat4.look_at(out, eye, look_at, up)
3✔
575
        local z_axis = (eye - look_at):normalize()
1✔
576
        local x_axis = up:cross(z_axis):normalize()
1✔
577
        local y_axis = z_axis:cross(x_axis)
1✔
578
        out[1] = x_axis.x
1✔
579
        out[2] = y_axis.x
1✔
580
        out[3] = z_axis.x
1✔
581
        out[4] = 0
1✔
582
        out[5] = x_axis.y
1✔
583
        out[6] = y_axis.y
1✔
584
        out[7] = z_axis.y
1✔
585
        out[8] = 0
1✔
586
        out[9] = x_axis.z
1✔
587
        out[10] = y_axis.z
1✔
588
        out[11] = z_axis.z
1✔
589
        out[12] = 0
1✔
590
        out[13] = -out[  1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
1✔
591
        out[14] = -out[  2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
1✔
592
        out[15] = -out[  3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
1✔
593
        out[16] = -out[  4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
1✔
594
        return out
1✔
595
end
596

597
--- Transform matrix to target a point.
598
-- @tparam mat4 out Matrix to store result
599
-- @tparam vec3 eye Location of viewer's view plane
600
-- @tparam vec3 center Location of object to view
601
-- @tparam vec3 up Up direction
602
-- @treturn mat4 out
603
function mat4.target(out, from, to, up)
3✔
604
        local z_axis = (from - to):normalize()
×
605
        local x_axis = up:cross(z_axis):normalize()
×
606
        local y_axis = z_axis:cross(x_axis)
×
607
        out[1] = x_axis.x
×
608
        out[2] = x_axis.y
×
609
        out[3] = x_axis.z
×
610
        out[4] = 0
×
611
        out[5] = y_axis.x
×
612
        out[6] = y_axis.y
×
613
        out[7] = y_axis.z
×
614
        out[8] = 0
×
615
        out[9] = z_axis.x
×
616
        out[10] = z_axis.y
×
617
        out[11] = z_axis.z
×
618
        out[12] = 0
×
619
        out[13] = from.x
×
620
        out[14] = from.y
×
621
        out[15] = from.z
×
622
        out[16] = 1
×
UNCOV
623
        return out
×
624
end
625

626
--- Transpose a matrix.
627
-- @tparam mat4 out Matrix to store the result
628
-- @tparam mat4 a Matrix to transpose
629
-- @treturn mat4 out
630
function mat4.transpose(out, a)
3✔
631
        tm4[1]  = a[1]
5✔
632
        tm4[2]  = a[5]
5✔
633
        tm4[3]  = a[9]
5✔
634
        tm4[4]  = a[13]
5✔
635
        tm4[5]  = a[2]
5✔
636
        tm4[6]  = a[6]
5✔
637
        tm4[7]  = a[10]
5✔
638
        tm4[8]  = a[14]
5✔
639
        tm4[9]  = a[3]
5✔
640
        tm4[10] = a[7]
5✔
641
        tm4[11] = a[11]
5✔
642
        tm4[12] = a[15]
5✔
643
        tm4[13] = a[4]
5✔
644
        tm4[14] = a[8]
5✔
645
        tm4[15] = a[12]
5✔
646
        tm4[16] = a[16]
5✔
647

648
        for i = 1, 16 do
85✔
649
                out[i] = tm4[i]
80✔
650
        end
651

652
        return out
5✔
653
end
654

655
--- Project a point into screen space
656
-- @tparam vec3 obj Object position in world space
657
-- @tparam mat4 mvp Projection matrix
658
-- @tparam table viewport XYWH of viewport
659
-- @treturn vec3 win
660
function mat4.project(obj, mvp, viewport)
3✔
661
        local point = mat4.mul_vec3_perspective(vec3(), mvp, obj)
4✔
662
        point.x = point.x * 0.5 + 0.5
4✔
663
        point.y = point.y * 0.5 + 0.5
4✔
664
        point.z = point.z * 0.5 + 0.5
4✔
665
        point.x = point.x * viewport[3] + viewport[1]
4✔
666
        point.y = point.y * viewport[4] + viewport[2]
4✔
667
        return point
4✔
668
end
669

670
--- Unproject a point from screen space to world space.
671
-- @tparam vec3 win Object position in screen space
672
-- @tparam mat4 mvp Projection matrix
673
-- @tparam table viewport XYWH of viewport
674
-- @treturn vec3 obj
675
function mat4.unproject(win, mvp, viewport)
3✔
676
        local point = vec3.clone(win)
1✔
677

678
        -- 0..n -> 0..1
679
        point.x = (point.x - viewport[1]) / viewport[3]
1✔
680
        point.y = (point.y - viewport[2]) / viewport[4]
1✔
681

682
        -- 0..1 -> -1..1
683
        point.x = point.x * 2 - 1
1✔
684
        point.y = point.y * 2 - 1
1✔
685
        point.z = point.z * 2 - 1
1✔
686

687
        return mat4.mul_vec3_perspective(point, tmp:invert(mvp), point)
1✔
688
end
689

690
--- Return a boolean showing if a table is or is not a mat4.
691
-- @tparam mat4 a Matrix to be tested
692
-- @treturn boolean is_mat4
693
function mat4.is_mat4(a)
3✔
694
        if type(a) == "cdata" then
65✔
UNCOV
695
                return ffi.istype("cpml_mat4", a)
×
696
        end
697

698
        if type(a) ~= "table" then
65✔
699
                return false
1✔
700
        end
701

702
        for i = 1, 16 do
936✔
703
                if type(a[i]) ~= "number" then
884✔
704
                        return false
12✔
705
                end
706
        end
707

708
        return true
52✔
709
end
710

711
--- Return whether any component is NaN
712
-- @tparam mat4 a Matrix to be tested
713
-- @treturn boolean if any component is NaN
714
function vec2.has_nan(a)
3✔
715
        for i = 1, 16 do
×
716
                if private.is_nan(a[i]) then
×
UNCOV
717
                        return true
×
718
                end
719
        end
UNCOV
720
        return false
×
721
end
722

723
--- Return a formatted string.
724
-- @tparam mat4 a Matrix to be turned into a string
725
-- @treturn string formatted
726
function mat4.to_string(a)
3✔
727
        local str = "[ "
1✔
728
        for i = 1, 16 do
17✔
729
                str = str .. string.format("%+0.3f", a[i])
16✔
730
                if i < 16 then
16✔
731
                        str = str .. ", "
15✔
732
                end
733
        end
734
        str = str .. " ]"
1✔
735
        return str
1✔
736
end
737

738
--- Convert a matrix to row vec4s.
739
-- @tparam mat4 a Matrix to be converted
740
-- @treturn table vec4s
741
function mat4.to_vec4s(a)
3✔
742
        return {
1✔
743
                { a[1],  a[2],  a[3],  a[4]  },
1✔
744
                { a[5],  a[6],  a[7],  a[8]  },
1✔
745
                { a[9],  a[10], a[11], a[12] },
1✔
746
                { a[13], a[14], a[15], a[16] }
1✔
747
        }
1✔
748
end
749

750
--- Convert a matrix to col vec4s.
751
-- @tparam mat4 a Matrix to be converted
752
-- @treturn table vec4s
753
function mat4.to_vec4s_cols(a)
3✔
754
        return {
1✔
755
                { a[1], a[5], a[9],  a[13] },
1✔
756
                { a[2], a[6], a[10], a[14] },
1✔
757
                { a[3], a[7], a[11], a[15] },
1✔
758
                { a[4], a[8], a[12], a[16] }
1✔
759
        }
1✔
760
end
761

762
-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
763
--- Convert a matrix to a quaternion.
764
-- @tparam mat4 a Matrix to be converted
765
-- @treturn quat out
766
function mat4.to_quat(a)
3✔
767
        identity(tmp):transpose(a)
1✔
768

769
        local w     = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
1✔
770
        local scale = w * 4
1✔
771
        local q     = quat.new(
2✔
772
                tmp[10] - tmp[7] / scale,
1✔
773
                tmp[3]  - tmp[9] / scale,
1✔
774
                tmp[5]  - tmp[2] / scale,
1✔
775
                w
776
        )
1✔
777

778
        return q:normalize(q)
1✔
779
end
780

781
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
782
--- Convert a matrix to a frustum.
783
-- @tparam mat4 a Matrix to be converted (projection * view)
784
-- @tparam boolean infinite Infinite removes the far plane
785
-- @treturn frustum out
786
function mat4.to_frustum(a, infinite)
3✔
787
        local t
788
        local frustum = {}
1✔
789

790
        -- Extract the LEFT plane
791
        frustum.left   = {}
1✔
792
        frustum.left.a = a[4]  + a[1]
1✔
793
        frustum.left.b = a[8]  + a[5]
1✔
794
        frustum.left.c = a[12] + a[9]
1✔
795
        frustum.left.d = a[16] + a[13]
1✔
796

797
        -- Normalize the result
798
        t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
1✔
799
        frustum.left.a = frustum.left.a / t
1✔
800
        frustum.left.b = frustum.left.b / t
1✔
801
        frustum.left.c = frustum.left.c / t
1✔
802
        frustum.left.d = frustum.left.d / t
1✔
803

804
        -- Extract the RIGHT plane
805
        frustum.right   = {}
1✔
806
        frustum.right.a = a[4]  - a[1]
1✔
807
        frustum.right.b = a[8]  - a[5]
1✔
808
        frustum.right.c = a[12] - a[9]
1✔
809
        frustum.right.d = a[16] - a[13]
1✔
810

811
        -- Normalize the result
812
        t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
1✔
813
        frustum.right.a = frustum.right.a / t
1✔
814
        frustum.right.b = frustum.right.b / t
1✔
815
        frustum.right.c = frustum.right.c / t
1✔
816
        frustum.right.d = frustum.right.d / t
1✔
817

818
        -- Extract the BOTTOM plane
819
        frustum.bottom   = {}
1✔
820
        frustum.bottom.a = a[4]  + a[2]
1✔
821
        frustum.bottom.b = a[8]  + a[6]
1✔
822
        frustum.bottom.c = a[12] + a[10]
1✔
823
        frustum.bottom.d = a[16] + a[14]
1✔
824

825
        -- Normalize the result
826
        t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
1✔
827
        frustum.bottom.a = frustum.bottom.a / t
1✔
828
        frustum.bottom.b = frustum.bottom.b / t
1✔
829
        frustum.bottom.c = frustum.bottom.c / t
1✔
830
        frustum.bottom.d = frustum.bottom.d / t
1✔
831

832
        -- Extract the TOP plane
833
        frustum.top   = {}
1✔
834
        frustum.top.a = a[4]  - a[2]
1✔
835
        frustum.top.b = a[8]  - a[6]
1✔
836
        frustum.top.c = a[12] - a[10]
1✔
837
        frustum.top.d = a[16] - a[14]
1✔
838

839
        -- Normalize the result
840
        t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
1✔
841
        frustum.top.a = frustum.top.a / t
1✔
842
        frustum.top.b = frustum.top.b / t
1✔
843
        frustum.top.c = frustum.top.c / t
1✔
844
        frustum.top.d = frustum.top.d / t
1✔
845

846
        -- Extract the NEAR plane
847
        frustum.near   = {}
1✔
848
        frustum.near.a = a[4]  + a[3]
1✔
849
        frustum.near.b = a[8]  + a[7]
1✔
850
        frustum.near.c = a[12] + a[11]
1✔
851
        frustum.near.d = a[16] + a[15]
1✔
852

853
        -- Normalize the result
854
        t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
1✔
855
        frustum.near.a = frustum.near.a / t
1✔
856
        frustum.near.b = frustum.near.b / t
1✔
857
        frustum.near.c = frustum.near.c / t
1✔
858
        frustum.near.d = frustum.near.d / t
1✔
859

860
        if not infinite then
1✔
861
                -- Extract the FAR plane
862
                frustum.far   = {}
1✔
863
                frustum.far.a = a[4]  - a[3]
1✔
864
                frustum.far.b = a[8]  - a[7]
1✔
865
                frustum.far.c = a[12] - a[11]
1✔
866
                frustum.far.d = a[16] - a[15]
1✔
867

868
                -- Normalize the result
869
                t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
1✔
870
                frustum.far.a = frustum.far.a / t
1✔
871
                frustum.far.b = frustum.far.b / t
1✔
872
                frustum.far.c = frustum.far.c / t
1✔
873
                frustum.far.d = frustum.far.d / t
1✔
874
        end
875

876
        return frustum
1✔
877
end
878

879
function mat4_mt.__index(t, k)
3✔
880
        if type(t) == "cdata" then
66✔
881
                if type(k) == "number" then
×
UNCOV
882
                        return t._m[k-1]
×
883
                end
884
        end
885

886
        return rawget(mat4, k)
66✔
887
end
888

889
function mat4_mt.__newindex(t, k, v)
3✔
890
        if type(t) == "cdata" then
×
891
                if type(k) == "number" then
×
UNCOV
892
                        t._m[k-1] = v
×
893
                end
894
        end
895
end
896

897
mat4_mt.__tostring = mat4.to_string
3✔
898

899
function mat4_mt.__call(_, a)
3✔
900
        return mat4.new(a)
39✔
901
end
902

903
function mat4_mt.__unm(a)
3✔
904
        return new():invert(a)
1✔
905
end
906

907
function mat4_mt.__eq(a, b)
3✔
908
        if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
4✔
UNCOV
909
                return false
×
910
        end
911

912
        for i = 1, 16 do
68✔
913
                if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
64✔
UNCOV
914
                        return false
×
915
                end
916
        end
917

918
        return true
4✔
919
end
920

921
function mat4_mt.__mul(a, b)
3✔
922
        precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))
13✔
923

924
        if vec3.is_vec3(b) then
13✔
925
                return mat4.mul_vec3_perspective(vec3(), a, b)
2✔
926
        end
927

928
        assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
11✔
929

930
        if mat4.is_mat4(b) then
11✔
931
                return new():mul(a, b)
6✔
932
        end
933

934
        return mat4.mul_vec4({}, a, b)
5✔
935
end
936

937
if status then
3✔
938
        xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
×
939
                ffi.metatype(new, mat4_mt)
×
UNCOV
940
        end, function() end)
×
941
end
942

943
return setmetatable({}, mat4_mt)
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc