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

excessive / cpml / 1

31 Mar 2022 01:05PM UTC coverage: 44.185% (-13.5%) from 57.717%
1

push

github

web-flow
Merge pull request #75 from idbrii/precond

Include offending type in precondition failure msg

50 of 60 new or added lines in 6 files covered. (83.33%)

205 existing lines in 6 files now uncovered.

76716 of 173626 relevant lines covered (44.18%)

1272.48 hits per line

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

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

19
-- Private constructor.
20
local function new(m)
21
        m = m or {
34,057✔
22
                0, 0, 0, 0,
32,629✔
23
                0, 0, 0, 0,
32,629✔
24
                0, 0, 0, 0,
32,629✔
25
                0, 0, 0, 0
32,629✔
26
        }
32,629✔
27
        m._m = m
34,057✔
28
        return setmetatable(m, mat4_mt)
34,057✔
29
end
30
 -- Convert matrix into identity
31
local function identity(m)
32
        m[1],  m[2],  m[3],  m[4]  = 1, 0, 0, 0
6,264✔
33
        m[5],  m[6],  m[7],  m[8]  = 0, 1, 0, 0
6,264✔
34
        m[9],  m[10], m[11], m[12] = 0, 0, 1, 0
6,264✔
35
        m[13], m[14], m[15], m[16] = 0, 0, 0, 1
6,264✔
36
        return m
6,264✔
37
end
38

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

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

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

64
        -- 4x4 matrix
65
        if type(a) == "table" and #a == 16 then
23,301✔
66
                for i = 1, 16 do
96,713✔
67
                        out[i] = tonumber(a[i])
91,024✔
68
                end
69

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

77
        -- 4 vec4s
78
        elseif type(a) == "table" and type(a[1]) == "table" then
17,044✔
79
                local idx = 1
568✔
80
                for i = 1, 4 do
2,840✔
81
                        for j = 1, 4 do
11,360✔
82
                                out[idx] = a[i][j]
9,088✔
83
                                idx = idx + 1
9,088✔
84
                        end
85
                end
86

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

95
        return out
23,301✔
96
end
97

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

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

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

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

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

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

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

6✔
155
        return out
2✔
156
end
2✔
157

2✔
158
--- Create a matrix from a transform.
159
-- @tparam vec3 trans Translation vector
3✔
160
-- @tparam quat rot Rotation quaternion
2✔
161
-- @tparam vec3 scale Scale vector
2✔
162
-- @treturn mat4 out
2✔
163
function mat4.from_transform(trans, rot, scale)
1,642✔
164
        local angle, axis = rot:to_angle_axis()
478✔
165
        local l = axis:len()
329✔
166

541✔
167
        if l == 0 then
486✔
168
                return new()
556✔
169
        end
539✔
170

527✔
171
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
551✔
172
        local c = cos(angle)
442✔
173
        local s = sin(angle)
600✔
174

474✔
175
        return new {
508✔
176
                x*x*(1-c)+c,   y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
552✔
177
                x*y*(1-c)-z*s, y*y*(1-c)+c,   y*z*(1-c)+x*s, 0,
515✔
178
                x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c,   0,
484✔
179
                trans.x, trans.y, trans.z, 1
404✔
180
        }
506✔
181
end
417✔
182

386✔
183
--- Create matrix from orthogonal.
363✔
184
-- @tparam number left
332✔
185
-- @tparam number right
304✔
186
-- @tparam number top
361✔
187
-- @tparam number bottom
317✔
188
-- @tparam number near
248✔
189
-- @tparam number far
245✔
190
-- @treturn mat4 out
224✔
191
function mat4.from_ortho(left, right, top, bottom, near, far)
203✔
192
        local out = new()
277✔
193
        out[1]    =  2 / (right - left)
195✔
194
        out[6]    =  2 / (top - bottom)
199✔
195
        out[11]   = -2 / (far - near)
184✔
196
        out[13]   = -((right + left) / (right - left))
255✔
197
        out[14]   = -((top + bottom) / (top - bottom))
264✔
UNCOV
198
        out[15]   = -((far + near) / (far - near))
103✔
UNCOV
199
        out[16]   =  1
101✔
200

108✔
201
        return out
176✔
202
end
171✔
203

207✔
204
--- Create matrix from perspective.
167✔
205
-- @tparam number fovy Field of view
97✔
206
-- @tparam number aspect Aspect ratio
106✔
207
-- @tparam number near Near plane
76✔
208
-- @tparam number far Far plane
72✔
209
-- @treturn mat4 out
71✔
210
function mat4.from_perspective(fovy, aspect, near, far)
98✔
211
        assert(aspect ~= 0)
198✔
212
        assert(near   ~= far)
262✔
213

356✔
214
        local t   = tan(rad(fovy) / 2)
362✔
215
        local out = new()
578✔
216
        out[1]    =  1 / (t * aspect)
790✔
217
        out[6]    =  1 / t
884✔
218
        out[11]   = -(far + near) / (far - near)
812✔
219
        out[12]   = -1
841✔
220
        out[15]   = -(2 * far * near) / (far - near)
1,156✔
221
        out[16]   =  0
1,272✔
222

1,382✔
223
        return out
1,251✔
224
end
1,361✔
225

1,332✔
226
-- Adapted from the Oculus SDK.
1,323✔
227
--- Create matrix from HMD perspective.
1,167✔
228
-- @tparam number tanHalfFov Tangent of half of the field of view
1,075✔
229
-- @tparam number zNear Near plane
1,126✔
230
-- @tparam number zFar Far plane
924✔
231
-- @tparam boolean flipZ Z axis is flipped or not
912✔
232
-- @tparam boolean farAtInfinity Far plane is infinite or not
816✔
233
-- @treturn mat4 out
788✔
234
function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
718✔
235
        -- CPML is right-handed and intended for GL, so these don't need to be arguments.
694✔
236
        local rightHanded = true
560✔
237
        local isOpenGL    = true
574✔
238

521✔
239
        local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
544✔
240
                local x_scale  = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
542✔
241
                local x_offset =     (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
433✔
242
                local y_scale  = 2 / (tanHalfFov.UpTan   + tanHalfFov.DownTan )
454✔
243
                local y_offset =     (tanHalfFov.UpTan   - tanHalfFov.DownTan ) * y_scale * 0.5
415✔
244

432✔
245
                local result = {
447✔
246
                        Scale  = vec2(x_scale, y_scale),
519✔
247
                        Offset = vec2(x_offset, y_offset)
485✔
248
                }
481✔
249

404✔
250
                -- Hey - why is that Y.Offset negated?
396✔
251
                -- It's because a projection matrix transforms from world coords with Y=up,
420✔
252
                -- whereas this is from NDC which is Y=down.
398✔
253
                 return result
348✔
254
        end
318✔
255

281✔
256
        if not flipZ and farAtInfinity then
284✔
257
                print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
329✔
258
                farAtInfinity = false
282✔
259
        end
268✔
260

200✔
261
         -- A projection matrix is very like a scaling from NDC, so we can start with that.
219✔
262
        local scaleAndOffset  = CreateNDCScaleAndOffsetFromFov(tanHalfFov)
216✔
UNCOV
263
        local handednessScale = rightHanded and -1.0 or 1.0
199✔
UNCOV
264
        local projection      = new()
211✔
265

209✔
266
        -- Produces X result, mapping clip edges to [-w,+w]
218✔
267
        projection[1] = scaleAndOffset.Scale.x
253✔
268
        projection[2] = 0
296✔
269
        projection[3] = handednessScale * scaleAndOffset.Offset.x
286✔
270
        projection[4] = 0
254✔
271

218✔
272
        -- Produces Y result, mapping clip edges to [-w,+w]
264✔
273
        -- Hey - why is that YOffset negated?
324✔
274
        -- It's because a projection matrix transforms from world coords with Y=up,
305✔
275
        -- whereas this is derived from an NDC scaling, which is Y=down.
282✔
276
        projection[5] = 0
239✔
277
        projection[6] = scaleAndOffset.Scale.y
230✔
278
        projection[7] = handednessScale * -scaleAndOffset.Offset.y
257✔
279
        projection[8] = 0
282✔
280

304✔
281
        -- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
295✔
282
        -- We'll just use some defaults for now.
315✔
283
        projection[9]  = 0
293✔
284
        projection[10] = 0
283✔
285

249✔
286
        if farAtInfinity then
202✔
287
                if isOpenGL then
255✔
288
                        -- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
268✔
289
                        projection[11] = -handednessScale
318✔
290
                        projection[12] = 2.0 * zNear
254✔
291
                else
213✔
292
                        projection[11] = 0
204✔
UNCOV
293
                        projection[12] = zNear
165✔
294
                end
174✔
295
        else
163✔
UNCOV
296
                if isOpenGL then
166✔
297
                        -- Clip range is [-w,+w], so 0 is at the middle of the range.
198✔
298
                        projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
163✔
UNCOV
299
                        projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
187✔
300
                else
178✔
301
                        -- Clip range is [0,+w], so 0 is at the start of the range.
192✔
302
                        projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
195✔
303
                        projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
166✔
304
                end
191✔
305
        end
148✔
306

117✔
307
        -- Produces W result (= Z in)
125✔
UNCOV
308
        projection[13] = 0
127✔
UNCOV
309
        projection[14] = 0
182✔
310
        projection[15] = handednessScale
182✔
311
        projection[16] = 0
178✔
312

194✔
313
        return projection:transpose(projection)
212✔
314
end
264✔
315

230✔
316
--- Clone a matrix.
209✔
317
-- @tparam mat4 a Matrix to clone
172✔
318
-- @treturn mat4 out
177✔
319
function mat4.clone(a)
224✔
320
        return new(a)
256✔
321
end
257✔
322

255✔
323
--- Multiply two matrices.
248✔
324
-- @tparam mat4 out Matrix to store the result
327✔
325
-- @tparam mat4 a Left hand operand
368✔
326
-- @tparam mat4 b Right hand operand
209✔
327
-- @treturn mat4 out Matrix equivalent to "apply b, then a"
190✔
328
function mat4.mul(out, a, b)
298✔
329
        tm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]
536✔
330
        tm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]
1,143✔
331
        tm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]
1,501✔
332
        tm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]
1,992✔
333
        tm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]
2,548✔
334
        tm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]
3,586✔
335
        tm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]
4,544✔
336
        tm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]
4,737✔
337
        tm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]
4,953✔
338
        tm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
5,295✔
339
        tm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
5,952✔
340
        tm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
6,634✔
341
        tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]
7,085✔
342
        tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
7,869✔
343
        tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
8,201✔
344
        tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
8,471✔
345

10,550✔
346
        for i=1, 16 do
11,378✔
347
                out[i] = tm4[i]
19,883✔
348
        end
23,893✔
349

21,973✔
350
        return out
22,892✔
351
end
31,104✔
352

41,285✔
353
--- Multiply a matrix and a vec4.
26,910✔
354
-- @tparam mat4 out Matrix to store the result
12,131✔
355
-- @tparam mat4 a Left hand operand
13,680✔
356
-- @tparam table b Right hand operand
20,206✔
357
-- @treturn mat4 out
25,901✔
358
function mat4.mul_vec4(out, a, b)
22,974✔
359
        tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]
18,519✔
360
        tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
17,458✔
361
        tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
16,314✔
362
        tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
13,941✔
363

12,354✔
364
        for i=1, 4 do
11,060✔
365
                out[i] = tv4[i]
12,158✔
366
        end
12,262✔
367

11,569✔
368
        return out
9,559✔
369
end
11,770✔
370

12,936✔
371
--- Invert a matrix.
8,413✔
372
-- @tparam mat4 out Matrix to store the result
6,484✔
373
-- @tparam mat4 a Matrix to invert
7,377✔
374
-- @treturn mat4 out
7,722✔
375
function mat4.invert(out, a)
7,924✔
376
        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]
7,495✔
377
        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]
7,369✔
378
        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]
6,127✔
379
        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]
5,116✔
380
        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]
5,470✔
381
        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]
4,898✔
382
        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]
4,134✔
383
        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,318✔
384
        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,207✔
385
        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,838✔
386
        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]
2,852✔
387
        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]
2,755✔
388
        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]
2,584✔
389
        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]
2,766✔
390
        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]
2,781✔
391
        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]
2,323✔
392

3,017✔
393
        for i=1, 16 do
3,135✔
394
                out[i] = tm4[i]
3,824✔
395
        end
4,615✔
396

4,364✔
397
        local det = a[1] * out[1] + a[2] * out[5] + a[3] * out[9] + a[4] * out[13]
4,321✔
398

5,803✔
399
        if det == 0 then return a end
7,372✔
400

4,798✔
401
        det = 1 / det
2,294✔
402

2,968✔
403
        for i = 1, 16 do
4,536✔
404
                out[i] = out[i] * det
6,765✔
405
        end
6,620✔
406

5,898✔
407
        return out
6,008✔
408
end
7,065✔
409

8,193✔
410
--- Scale a matrix.
5,442✔
411
-- @tparam mat4 out Matrix to store the result
2,871✔
412
-- @tparam mat4 a Matrix to scale
2,926✔
413
-- @tparam vec3 s Scalar
3,944✔
414
-- @treturn mat4 out
4,970✔
415
function mat4.scale(out, a, s)
4,205✔
416
        identity(tmp)
3,723✔
417
        tmp[1]  = s.x
3,348✔
418
        tmp[6]  = s.y
2,880✔
419
        tmp[11] = s.z
2,557✔
420

2,391✔
421
        return out:mul(tmp, a)
2,077✔
422
end
1,811✔
423

1,689✔
424
--- Rotate a matrix.
1,837✔
425
-- @tparam mat4 out Matrix to store the result
1,373✔
426
-- @tparam mat4 a Matrix to rotate
1,330✔
427
-- @tparam number angle Angle to rotate by (in radians)
1,322✔
428
-- @tparam vec3 axis Axis to rotate on
919✔
429
-- @treturn mat4 out
930✔
430
function mat4.rotate(out, a, angle, axis)
913✔
431
        if type(angle) == "table" or type(angle) == "cdata" then
1,121✔
432
                angle, axis = angle:to_angle_axis()
1,542✔
433
        end
1,406✔
434

1,176✔
435
        local l = axis:len()
784✔
436

956✔
437
        if l == 0 then
891✔
UNCOV
438
                return a
683✔
439
        end
653✔
440

792✔
441
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
813✔
442
        local c = cos(angle)
935✔
443
        local s = sin(angle)
903✔
UNCOV
444

778✔
445
        identity(tmp)
847✔
446
        tmp[1]  = x * x * (1 - c) + c
1,186✔
447
        tmp[2]  = y * x * (1 - c) + z * s
1,331✔
448
        tmp[3]  = x * z * (1 - c) - y * s
1,260✔
449
        tmp[5]  = x * y * (1 - c) - z * s
1,273✔
450
        tmp[6]  = y * y * (1 - c) + c
1,381✔
451
        tmp[7]  = y * z * (1 - c) + x * s
1,569✔
452
        tmp[9]  = x * z * (1 - c) + y * s
1,571✔
453
        tmp[10] = y * z * (1 - c) - x * s
1,682✔
454
        tmp[11] = z * z * (1 - c) + c
1,669✔
455

1,688✔
456
        return out:mul(tmp, a)
1,676✔
457
end
1,656✔
458

1,578✔
459
--- Translate a matrix.
1,486✔
460
-- @tparam mat4 out Matrix to store the result
1,415✔
461
-- @tparam mat4 a Matrix to translate
1,309✔
462
-- @tparam vec3 t Translation vector
1,300✔
463
-- @treturn mat4 out
1,106✔
464
function mat4.translate(out, a, t)
1,136✔
465
        identity(tmp)
1,098✔
466
        tmp[13] = t.x
1,130✔
467
        tmp[14] = t.y
1,035✔
468
        tmp[15] = t.z
1,021✔
469

1,078✔
470
        return out:mul(tmp, a)
1,078✔
471
end
1,076✔
472

942✔
473
--- Shear a matrix.
891✔
474
-- @tparam mat4 out Matrix to store the result
781✔
475
-- @tparam mat4 a Matrix to translate
762✔
476
-- @tparam number yx
828✔
477
-- @tparam number zx
683✔
478
-- @tparam number xy
698✔
479
-- @tparam number zy
728✔
480
-- @tparam number xz
681✔
481
-- @tparam number yz
608✔
482
-- @treturn mat4 out
463✔
483
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
433✔
484
        identity(tmp)
458✔
485
        tmp[2]  = yx or 0
427✔
486
        tmp[3]  = zx or 0
398✔
487
        tmp[5]  = xy or 0
441✔
488
        tmp[7]  = zy or 0
532✔
489
        tmp[9]  = xz or 0
635✔
490
        tmp[10] = yz or 0
505✔
491

474✔
492
        return out:mul(tmp, a)
380✔
493
end
469✔
494

444✔
495
--- Reflect a matrix across a plane.
440✔
496
-- @tparam mat4 Matrix to store the result
373✔
497
-- @tparam a Matrix to reflect
376✔
498
-- @tparam vec3 position A point on the plane
365✔
499
-- @tparam vec3 normal The (normalized!) normal vector of the plane
336✔
500
function mat4.reflect(out, a, position, normal)
313✔
501
        local nx, ny, nz = normal:unpack()
374✔
502
        local d = -position:dot(normal)
353✔
503
        tmp[1] = 1 - 2 * nx ^ 2
379✔
504
        tmp[2] = 2 * nx * ny
366✔
505
        tmp[3] = -2 * nx * nz
440✔
506
        tmp[4] = 0
511✔
507
        tmp[5] = -2 * nx * ny
381✔
508
        tmp[6] = 1 - 2 * ny ^ 2
390✔
509
        tmp[7] = -2 * ny * nz
400✔
510
        tmp[8] = 0
462✔
511
        tmp[9] = -2 * nx * nz
499✔
512
        tmp[10] = -2 * ny * nz
486✔
513
        tmp[11] = 1 - 2 * nz ^ 2
513✔
514
        tmp[12] = 0
518✔
515
        tmp[13] = -2 * nx * d
520✔
516
        tmp[14] = -2 * ny * d
537✔
517
        tmp[15] = -2 * nz * d
533✔
518
        tmp[16] = 1
525✔
519

527✔
520
        return out:mul(tmp, a)
496✔
521
end
515✔
522

471✔
523
--- Transform matrix to look at a point.
466✔
524
-- @tparam mat4 out Matrix to store result
394✔
525
-- @tparam mat4 a Matrix to transform
364✔
526
-- @tparam vec3 eye Location of viewer's view plane
375✔
527
-- @tparam vec3 center Location of object to view
318✔
528
-- @tparam vec3 up Up direction
305✔
529
-- @treturn mat4 out
293✔
530
function mat4.look_at(out, a, eye, look_at, up)
277✔
531
        local z_axis = (eye - look_at):normalize()
350✔
532
        local x_axis = up:cross(z_axis):normalize()
300✔
533
        local y_axis = z_axis:cross(x_axis)
328✔
534
        out[1] = x_axis.x
327✔
535
        out[2] = y_axis.x
427✔
536
        out[3] = z_axis.x
478✔
537
        out[4] = 0
372✔
538
        out[5] = x_axis.y
382✔
539
        out[6] = y_axis.y
395✔
540
        out[7] = z_axis.y
452✔
541
        out[8] = 0
494✔
542
        out[9] = x_axis.z
476✔
543
        out[10] = y_axis.z
507✔
544
        out[11] = z_axis.z
501✔
545
        out[12] = 0
519✔
546
        out[13] = -out[  1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
519✔
547
        out[14] = -out[  2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
526✔
548
        out[15] = -out[  3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
517✔
549
        out[16] = -out[  4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
527✔
550

532✔
551
  return out
510✔
552
end
507✔
553

485✔
554
--- Transpose a matrix.
451✔
555
-- @tparam mat4 out Matrix to store the result
402✔
556
-- @tparam mat4 a Matrix to transpose
374✔
557
-- @treturn mat4 out
396✔
558
function mat4.transpose(out, a)
352✔
559
        tm4[1]  = a[1]
456✔
560
        tm4[2]  = a[5]
552✔
561
        tm4[3]  = a[9]
665✔
562
        tm4[4]  = a[13]
763✔
563
        tm4[5]  = a[2]
950✔
564
        tm4[6]  = a[6]
1,216✔
565
        tm4[7]  = a[10]
1,341✔
566
        tm4[8]  = a[14]
1,386✔
567
        tm4[9]  = a[3]
1,430✔
568
        tm4[10] = a[7]
1,561✔
569
        tm4[11] = a[11]
1,731✔
570
        tm4[12] = a[15]
1,875✔
571
        tm4[13] = a[4]
1,983✔
572
        tm4[14] = a[8]
2,174✔
573
        tm4[15] = a[12]
2,256✔
574
        tm4[16] = a[16]
2,313✔
575

2,848✔
576
        for i=1, 16 do
3,058✔
577
                out[i] = tm4[i]
5,293✔
578
        end
6,342✔
579

5,829✔
580
        return out
6,061✔
581
end
8,228✔
582

10,896✔
583
-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L518
7,105✔
584
--- Project a matrix from world space to screen space.
3,215✔
585
-- @tparam vec3 obj Object position in world space
3,624✔
586
-- @tparam mat4 view View matrix
5,336✔
587
-- @tparam mat4 projection Projection matrix
6,835✔
588
-- @tparam table viewport XYWH of viewport
6,035✔
589
-- @treturn vec3 win
4,858✔
590
function mat4.project(obj, view, projection, viewport)
4,460✔
591
        local position = { obj.x, obj.y, obj.z, 1 }
4,171✔
592

3,506✔
593
        mat4.mul_vec4(position, view,       position)
2,896✔
594
        mat4.mul_vec4(position, projection, position)
2,490✔
595

2,329✔
596
        position[1] = position[1] / position[4] * 0.5 + 0.5
2,327✔
597
        position[2] = position[2] / position[4] * 0.5 + 0.5
2,172✔
598
        position[3] = position[3] / position[4] * 0.5 + 0.5
1,667✔
599

1,914✔
600
        position[1] = position[1] * viewport[3] + viewport[1]
1,657✔
601
        position[2] = position[2] * viewport[4] + viewport[2]
1,252✔
602

1,452✔
603
        return vec3(position[1], position[2], position[3])
1,526✔
604
end
1,312✔
605

1,154✔
606
-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L544
1,280✔
607
--- Unproject a matrix from screen space to world space.
1,396✔
608
-- @tparam vec3 win Object position in screen space
1,059✔
609
-- @tparam mat4 view View matrix
816✔
610
-- @tparam mat4 projection Projection matrix
936✔
611
-- @tparam table viewport XYWH of viewport
887✔
612
-- @treturn vec3 obj
741✔
613
function mat4.unproject(win, view, projection, viewport)
581✔
614
        local position = { win.x, win.y, win.z, 1 }
625✔
615

722✔
616
        position[1] = (position[1] - viewport[1]) / viewport[3]
542✔
617
        position[2] = (position[2] - viewport[2]) / viewport[4]
469✔
618

525✔
619
        position[1] = position[1] * 2 - 1
651✔
620
        position[2] = position[2] * 2 - 1
515✔
621
        position[3] = position[3] * 2 - 1
342✔
622

547✔
623
        tmp:mul(projection, view):invert(tmp)
565✔
624
        mat4.mul_vec4(position, tmp, position)
442✔
625

432✔
626
        position[1] = position[1] / position[4]
519✔
627
        position[2] = position[2] / position[4]
500✔
628
        position[3] = position[3] / position[4]
480✔
629

527✔
630
        return vec3(position[1], position[2], position[3])
400✔
631
end
404✔
632

413✔
633
--- Return a boolean showing if a table is or is not a mat4.
507✔
634
-- @tparam mat4 a Matrix to be tested
437✔
635
-- @treturn boolean is_mat4
339✔
636
function mat4.is_mat4(a)
674✔
637
        if type(a) == "cdata" then
685✔
638
                return ffi.istype("cpml_mat4", a)
1,816✔
639
        end
1,299✔
640

1,730✔
641
        if type(a) ~= "table" then
2,303✔
642
                return false
4,733✔
643
        end
4,311✔
UNCOV
644

5,941✔
645
        for i = 1, 16 do
8,201✔
646
                if type(a[i]) ~= "number" then
25,735✔
647
                        return false
34,862✔
648
                end
29,758✔
649
        end
32,552✔
650

49,070✔
651
        return true
68,759✔
652
end
43,718✔
653

15,762✔
654
--- Return whether any component is NaN
18,174✔
655
-- @tparam mat4 a Matrix to be tested
31,013✔
656
-- @treturn boolean if any component is NaN
44,919✔
657
function vec2.has_nan(a)
40,015✔
658
        for i=1, 16 do
30,635✔
659
                if private.is_nan(a[i]) then
27,284✔
660
                        return true
25,423✔
661
                end
21,551✔
662
        end
17,832✔
663
        return false
14,197✔
UNCOV
664
end
12,566✔
UNCOV
665

11,961✔
UNCOV
666
--- Return a formatted string.
11,595✔
667
-- @tparam mat4 a Matrix to be turned into a string
8,456✔
668
-- @treturn string formatted
9,349✔
UNCOV
669
function mat4.to_string(a)
7,389✔
670
        local str = "[ "
4,566✔
671
        for i = 1, 16 do
5,575✔
672
                str = str .. string.format("%+0.3f", a[i])
6,782✔
673
                if i < 16 then
5,419✔
674
                        str = str .. ", "
5,583✔
675
                end
6,262✔
676
        end
7,812✔
677
        str = str .. " ]"
6,429✔
678
        return str
4,568✔
679
end
5,955✔
680

4,907✔
681
--- Convert a matrix to row vec4s.
3,742✔
682
-- @tparam mat4 a Matrix to be converted
3,200✔
683
-- @treturn table vec4s
3,362✔
684
function mat4.to_vec4s(a)
4,637✔
685
        return {
3,181✔
686
                { a[1],  a[2],  a[3],  a[4]  },
2,335✔
687
                { a[5],  a[6],  a[7],  a[8]  },
2,263✔
688
                { a[9],  a[10], a[11], a[12] },
2,591✔
689
                { a[13], a[14], a[15], a[16] }
2,502✔
690
        }
1,262✔
691
end
2,211✔
692

2,069✔
693
--- Convert a matrix to col vec4s.
866✔
694
-- @tparam mat4 a Matrix to be converted
946✔
695
-- @treturn table vec4s
1,351✔
696
function mat4.to_vec4s_cols(a)
1,200✔
697
        return {
1,377✔
698
                { a[1], a[5], a[9],  a[13] },
1,209✔
699
                { a[2], a[6], a[10], a[14] },
687✔
700
                { a[3], a[7], a[11], a[15] },
660✔
701
                { a[4], a[8], a[12], a[16] }
779✔
702
        }
1,347✔
703
end
1,251✔
704

571✔
705
-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
1,140✔
706
--- Convert a matrix to a quaternion.
1,060✔
707
-- @tparam mat4 a Matrix to be converted
1,152✔
708
-- @treturn quat out
1,031✔
709
function mat4.to_quat(a)
405✔
710
        identity(tmp):transpose(a)
473✔
711

472✔
712
        local w     = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
447✔
713
        local scale = w * 4
464✔
714
        local q     = quat.new(
490✔
715
                tmp[10] - tmp[7] / scale,
588✔
716
                tmp[3]  - tmp[9] / scale,
404✔
717
                tmp[5]  - tmp[2] / scale,
441✔
718
                w
445✔
719
        )
522✔
720

549✔
721
        return q:normalize(q)
425✔
722
end
485✔
723

465✔
724
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
454✔
725
--- Convert a matrix to a frustum.
404✔
726
-- @tparam mat4 a Matrix to be converted (projection * view)
400✔
727
-- @tparam boolean infinite Infinite removes the far plane
401✔
728
-- @treturn frustum out
340✔
729
function mat4.to_frustum(a, infinite)
292✔
730
        local t
960✔
731
        local frustum = {}
1,469✔
732

1,509✔
733
        -- Extract the LEFT plane
872✔
734
        frustum.left   = {}
387✔
735
        frustum.left.a = a[4]  + a[1]
400✔
736
        frustum.left.b = a[8]  + a[5]
313✔
737
        frustum.left.c = a[12] + a[9]
316✔
738
        frustum.left.d = a[16] + a[13]
281✔
739

355✔
740
        -- Normalize the result
406✔
741
        t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
356✔
742
        frustum.left.a = frustum.left.a / t
383✔
743
        frustum.left.b = frustum.left.b / t
360✔
744
        frustum.left.c = frustum.left.c / t
364✔
745
        frustum.left.d = frustum.left.d / t
328✔
746

387✔
747
        -- Extract the RIGHT plane
412✔
748
        frustum.right   = {}
419✔
749
        frustum.right.a = a[4]  - a[1]
394✔
750
        frustum.right.b = a[8]  - a[5]
390✔
751
        frustum.right.c = a[12] - a[9]
354✔
752
        frustum.right.d = a[16] - a[13]
365✔
753

382✔
754
        -- Normalize the result
425✔
755
        t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
421✔
756
        frustum.right.a = frustum.right.a / t
429✔
757
        frustum.right.b = frustum.right.b / t
417✔
758
        frustum.right.c = frustum.right.c / t
427✔
759
        frustum.right.d = frustum.right.d / t
396✔
760

442✔
761
        -- Extract the BOTTOM plane
438✔
762
        frustum.bottom   = {}
425✔
763
        frustum.bottom.a = a[4]  + a[2]
411✔
764
        frustum.bottom.b = a[8]  + a[6]
397✔
765
        frustum.bottom.c = a[12] + a[10]
383✔
766
        frustum.bottom.d = a[16] + a[14]
349✔
767

406✔
768
        -- Normalize the result
434✔
769
        t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
424✔
770
        frustum.bottom.a = frustum.bottom.a / t
416✔
771
        frustum.bottom.b = frustum.bottom.b / t
402✔
772
        frustum.bottom.c = frustum.bottom.c / t
390✔
773
        frustum.bottom.d = frustum.bottom.d / t
358✔
774

408✔
775
        -- Extract the TOP plane
444✔
776
        frustum.top   = {}
424✔
777
        frustum.top.a = a[4]  - a[2]
415✔
778
        frustum.top.b = a[8]  - a[6]
396✔
779
        frustum.top.c = a[12] - a[10]
386✔
780
        frustum.top.d = a[16] - a[14]
354✔
781

413✔
782
        -- Normalize the result
439✔
783
        t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
427✔
784
        frustum.top.a = frustum.top.a / t
416✔
785
        frustum.top.b = frustum.top.b / t
398✔
786
        frustum.top.c = frustum.top.c / t
389✔
787
        frustum.top.d = frustum.top.d / t
359✔
788

409✔
789
        -- Extract the NEAR plane
440✔
790
        frustum.near   = {}
421✔
791
        frustum.near.a = a[4]  + a[3]
418✔
792
        frustum.near.b = a[8]  + a[7]
396✔
793
        frustum.near.c = a[12] + a[11]
385✔
794
        frustum.near.d = a[16] + a[15]
361✔
795

412✔
796
        -- Normalize the result
442✔
797
        t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
425✔
798
        frustum.near.a = frustum.near.a / t
418✔
799
        frustum.near.b = frustum.near.b / t
402✔
800
        frustum.near.c = frustum.near.c / t
388✔
801
        frustum.near.d = frustum.near.d / t
363✔
802

416✔
803
        if not infinite then
438✔
804
                -- Extract the FAR plane
454✔
805
                frustum.far   = {}
405✔
806
                frustum.far.a = a[4]  - a[3]
406✔
807
                frustum.far.b = a[8]  - a[7]
385✔
808
                frustum.far.c = a[12] - a[11]
393✔
809
                frustum.far.d = a[16] - a[15]
417✔
810

415✔
811
                -- Normalize the result
441✔
812
                t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
414✔
813
                frustum.far.a = frustum.far.a / t
422✔
814
                frustum.far.b = frustum.far.b / t
419✔
815
                frustum.far.c = frustum.far.c / t
389✔
816
                frustum.far.d = frustum.far.d / t
368✔
817
        end
414✔
818

445✔
819
        return frustum
484✔
820
end
419✔
821

441✔
822
function mat4_mt.__index(t, k)
720✔
823
        if type(t) == "cdata" then
634✔
824
                if type(k) == "number" then
2,478✔
825
                        return t._m[k-1]
1,722✔
826
                end
2,055✔
827
        end
2,111✔
828

4,228✔
829
        return rawget(mat4, k)
4,319✔
UNCOV
830
end
2,972✔
UNCOV
831

2,251✔
832
function mat4_mt.__newindex(t, k, v)
2,929✔
833
        if type(t) == "cdata" then
4,167✔
834
                if type(k) == "number" then
6,127✔
835
                        t._m[k-1] = v
5,743✔
836
                end
2,409✔
837
        end
2,404✔
838
end
2,533✔
UNCOV
839

3,261✔
UNCOV
840
mat4_mt.__tostring = mat4.to_string
3,387✔
UNCOV
841

2,512✔
842
function mat4_mt.__call(_, a)
2,676✔
843
        return mat4.new(a)
2,450✔
844
end
3,481✔
845

2,368✔
846
function mat4_mt.__unm(a)
2,958✔
847
        return new():invert(a)
2,166✔
848
end
3,499✔
849

3,568✔
850
function mat4_mt.__eq(a, b)
1,637✔
851
        if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
1,447✔
852
                return false
2,188✔
853
        end
2,243✔
854

2,923✔
855
        for i = 1, 16 do
2,296✔
856
                if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
3,959✔
857
                        return false
4,276✔
UNCOV
858
                end
3,821✔
859
        end
3,793✔
860

5,249✔
861
        return true
6,552✔
862
end
4,533✔
UNCOV
863

2,046✔
864
function mat4_mt.__mul(a, b)
2,272✔
865
        precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))
3,217✔
866

5,013✔
867
        if vec3.is_vec3(b) then
4,173✔
868
                return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))
3,776✔
869
        end
3,524✔
870

3,781✔
871
        assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
3,219✔
872

3,165✔
873
        if mat4.is_mat4(b) then
2,891✔
874
                return new():mul(a, b)
2,546✔
875
        end
2,688✔
876

2,959✔
877
        return mat4.mul_vec4({}, a, b)
2,857✔
878
end
2,831✔
879

2,763✔
880
if status then
1,874✔
881
        xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
1,947✔
882
                ffi.metatype(new, mat4_mt)
2,066✔
883
        end, function() end)
1,934✔
884
end
1,771✔
885

1,709✔
886
return setmetatable({}, mat4_mt)
1,814✔
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