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

excessive / cpml / 1

20 Apr 2022 10:56PM UTC coverage: 45.192% (+1.0%) from 44.185%
1

push

github

web-flow
Merge pull request #66 from aki-cat/master

Implement scale to mat4.from_transform method

33 of 33 new or added lines in 2 files covered. (100.0%)

38 existing lines in 2 files now uncovered.

44574 of 98633 relevant lines covered (45.19%)

1232.55 hits per line

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

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

3✔
19
-- Private constructor.
20
local function new(m)
21
        m = m or {
18,480✔
22
                0, 0, 0, 0,
17,616✔
23
                0, 0, 0, 0,
17,613✔
24
                0, 0, 0, 0,
17,613✔
25
                0, 0, 0, 0
17,613✔
26
        }
17,613✔
27
        m._m = m
18,537✔
28
        return setmetatable(m, mat4_mt)
18,540✔
29
end
60✔
30
 -- Convert matrix into identity
31
local function identity(m)
32
        m[1],  m[2],  m[3],  m[4]  = 1, 0, 0, 0
3,388✔
33
        m[5],  m[6],  m[7],  m[8]  = 0, 1, 0, 0
3,399✔
34
        m[9],  m[10], m[11], m[12] = 0, 0, 1, 0
3,399✔
35
        m[13], m[14], m[15], m[16] = 0, 0, 0, 1
3,399✔
36
        return m
3,399✔
37
end
11✔
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
924✔
42
   --  status, ffi = pcall(require, "ffi")
3✔
43
    if status then
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()
924✔
51
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
927✔
52
local tv4 = { 0, 0, 0, 0 }
927✔
53

3✔
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
3✔
61
function mat4.new(a)
962✔
62
        local out = new()
12,590✔
63

41✔
64
        -- 4x4 matrix
41✔
65
        if type(a) == "table" and #a == 16 then
12,757✔
66
                for i = 1, 16 do
52,391✔
67
                        out[i] = tonumber(a[i])
49,290✔
68
                end
160✔
69

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

1✔
77
        -- 4 vec4s
30✔
78
        elseif type(a) == "table" and type(a[1]) == "table" then
9,211✔
79
                local idx = 1
342✔
80
                for i = 1, 4 do
1,556✔
81
                        for j = 1, 4 do
6,161✔
82
                                out[idx] = a[i][j]
4,948✔
83
                                idx = idx + 1
4,928✔
84
                        end
16✔
85
                end
86

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

41✔
95
        return out
12,587✔
96
end
41✔
97

98
--- Create an identity matrix.
99
-- @tparam mat4 a Matrix to overwrite
100
-- @treturn mat4 out
3✔
101
function mat4.identity(a)
922✔
102
        return identity(a or new())
309✔
103
end
2✔
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
3✔
109
function mat4.from_angle_axis(angle, axis)
918✔
110
        local l = axis:len()
6✔
111
        if l == 0 then
×
112
                return new()
113
        end
114

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

119
        return new {
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
3✔
130
function mat4.from_quaternion(q)
918✔
131
        return mat4.from_angle_axis(q:to_angle_axis())
6✔
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
3✔
138
function mat4.from_direction(direction, up)
918✔
139
        local forward = vec3.normalize(direction)
6✔
140
        local side = vec3.cross(forward, up):normalize()
×
141
        local new_up = vec3.cross(side, forward):normalize()
142

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

155
        return out
156
end
157

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

5✔
166
        local sm = new {
611✔
167
                scale.x, 0,       0,       0,
312✔
168
                0,       scale.y, 0,       0,
309✔
169
                0,       0,       scale.z, 0,
309✔
170
                0,       0,       0,       1,
309✔
171
        }
308✔
172

5✔
173
        local rm = new {
611✔
174
                1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0,
312✔
175
                2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0,
309✔
176
                2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0,
309✔
177
                0, 0, 0, 1
309✔
178
        }
308✔
179

4✔
180
        local rsm = rm * sm
305✔
181

4✔
182
        rsm[13] = trans.x
306✔
183
        rsm[14] = trans.y
309✔
184
        rsm[15] = trans.z
308✔
185

4✔
186
        return rsm
305✔
187
end
3✔
188

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

207
        return out
208
end
209

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

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

16✔
229
        return out
1,220✔
230
end
12✔
231

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

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

5✔
251
                local result = {
306✔
252
                        Scale  = vec2(x_scale, y_scale),
308✔
253
                        Offset = vec2(x_offset, y_offset)
308✔
254
                }
4✔
255

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

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

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

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

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

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

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

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

7✔
319
        return projection:transpose(projection)
305✔
320
end
3✔
321

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

3✔
329
--- Multiply two matrices.
330
-- @tparam mat4 out Matrix to store the result
331
-- @tparam mat4 a Left hand operand
332
-- @tparam mat4 b Right hand operand
333
-- @treturn mat4 out Matrix equivalent to "apply b, then a"
3✔
334
function mat4.mul(out, a, b)
925✔
335
        tm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]
5,766✔
336
        tm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]
5,823✔
337
        tm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]
5,871✔
338
        tm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]
5,871✔
339
        tm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]
5,871✔
340
        tm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]
5,871✔
341
        tm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]
5,871✔
342
        tm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]
5,871✔
343
        tm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]
5,871✔
344
        tm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
5,871✔
345
        tm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
5,871✔
346
        tm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
5,871✔
347
        tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]
5,871✔
348
        tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
5,871✔
349
        tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
5,871✔
350
        tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
5,852✔
351

437✔
352
        for i=1, 16 do
97,907✔
353
                out[i] = tm4[i]
92,777✔
354
        end
1,881✔
355

931✔
356
        return out
5,738✔
357
end
57✔
358

57✔
359
--- Multiply a matrix and a vec4.
360
-- @tparam mat4 out Matrix to store the result
361
-- @tparam mat4 a Left hand operand
362
-- @tparam table b Right hand operand
363
-- @treturn mat4 out
3✔
364
function mat4.mul_vec4(out, a, b)
919✔
365
        tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]
3,948✔
366
        tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
3,987✔
367
        tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
4,017✔
368
        tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
4,004✔
369

143✔
370
        for i=1, 4 do
19,721✔
371
                out[i] = tv4[i]
15,899✔
372
        end
351✔
373

169✔
374
        return out
3,926✔
375
end
39✔
376

39✔
377
--- Invert a matrix.
378
-- @tparam mat4 out Matrix to store the result
379
-- @tparam mat4 a Matrix to invert
380
-- @treturn mat4 out
3✔
381
function mat4.invert(out, a)
909✔
382
        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]
918✔
383
        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]
927✔
384
        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]
927✔
385
        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]
927✔
386
        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]
927✔
387
        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]
927✔
388
        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]
927✔
389
        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]
927✔
390
        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]
927✔
391
        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]
927✔
392
        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]
927✔
393
        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]
927✔
394
        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]
927✔
395
        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]
927✔
396
        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]
927✔
397
        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]
924✔
398

69✔
399
        for i=1, 16 do
15,459✔
400
                out[i] = tm4[i]
14,649✔
401
        end
297✔
402

147✔
403
        local det = a[1] * out[1] + a[2] * out[5] + a[3] * out[9] + a[4] * out[13]
906✔
404

12✔
405
        if det == 0 then return a end
915✔
406

12✔
407
        det = 1 / det
915✔
408

60✔
409
        for i = 1, 16 do
15,459✔
410
                out[i] = out[i] * det
14,649✔
411
        end
297✔
412

147✔
413
        return out
906✔
414
end
9✔
415

9✔
416
--- Scale a matrix.
417
-- @tparam mat4 out Matrix to store the result
418
-- @tparam mat4 a Matrix to scale
419
-- @tparam vec3 s Scalar
420
-- @treturn mat4 out
3✔
421
function mat4.scale(out, a, s)
907✔
422
        identity(tmp)
312✔
423
        tmp[1]  = s.x
315✔
424
        tmp[6]  = s.y
309✔
425
        tmp[11] = s.z
308✔
426

7✔
427
        return out:mul(tmp, a)
305✔
428
end
3✔
429

3✔
430
--- Rotate a matrix.
431
-- @tparam mat4 out Matrix to store the result
432
-- @tparam mat4 a Matrix to rotate
433
-- @tparam number angle Angle to rotate by (in radians)
434
-- @tparam vec3 axis Axis to rotate on
435
-- @treturn mat4 out
3✔
436
function mat4.rotate(out, a, angle, axis)
910✔
437
        if type(angle) == "table" or type(angle) == "cdata" then
1,217✔
UNCOV
438
                angle, axis = angle:to_angle_axis()
21✔
439
        end
12✔
440

4✔
441
        local l = axis:len()
1,204✔
442

16✔
443
        if l == 0 then
1,216✔
444
                return a
16✔
445
        end
12✔
446

8✔
447
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
1,208✔
448
        local c = cos(angle)
1,220✔
449
        local s = sin(angle)
1,228✔
450

32✔
451
        identity(tmp)
1,224✔
452
        tmp[1]  = x * x * (1 - c) + c
1,224✔
453
        tmp[2]  = y * x * (1 - c) + z * s
1,232✔
454
        tmp[3]  = x * z * (1 - c) - y * s
1,236✔
455
        tmp[5]  = x * y * (1 - c) - z * s
1,236✔
456
        tmp[6]  = y * y * (1 - c) + c
1,236✔
457
        tmp[7]  = y * z * (1 - c) + x * s
1,236✔
458
        tmp[9]  = x * z * (1 - c) + y * s
1,236✔
459
        tmp[10] = y * z * (1 - c) - x * s
1,236✔
460
        tmp[11] = z * z * (1 - c) + c
1,232✔
461

32✔
462
        return out:mul(tmp, a)
1,220✔
463
end
16✔
464

12✔
465
--- Translate a matrix.
4✔
466
-- @tparam mat4 out Matrix to store the result
467
-- @tparam mat4 a Matrix to translate
468
-- @tparam vec3 t Translation vector
469
-- @treturn mat4 out
3✔
470
function mat4.translate(out, a, t)
906✔
471
        identity(tmp)
915✔
472
        tmp[13] = t.x
924✔
473
        tmp[14] = t.y
927✔
474
        tmp[15] = t.z
924✔
475

24✔
476
        return out:mul(tmp, a)
915✔
477
end
12✔
478

9✔
479
--- Shear a matrix.
3✔
480
-- @tparam mat4 out Matrix to store the result
481
-- @tparam mat4 a Matrix to translate
482
-- @tparam number yx
483
-- @tparam number zx
484
-- @tparam number xy
485
-- @tparam number zy
486
-- @tparam number xz
487
-- @tparam number yz
488
-- @treturn mat4 out
3✔
489
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
904✔
490
        identity(tmp)
311✔
491
        tmp[2]  = yx or 0
314✔
492
        tmp[3]  = zx or 0
311✔
493
        tmp[5]  = xy or 0
309✔
494
        tmp[7]  = zy or 0
309✔
495
        tmp[9]  = xz or 0
309✔
496
        tmp[10] = yz or 0
308✔
497

8✔
498
        return out:mul(tmp, a)
305✔
499
end
4✔
500

3✔
501
--- Reflect a matrix across a plane.
1✔
502
-- @tparam mat4 Matrix to store the result
503
-- @tparam a Matrix to reflect
504
-- @tparam vec3 position A point on the plane
505
-- @tparam vec3 normal The (normalized!) normal vector of the plane
3✔
506
function mat4.reflect(out, a, position, normal)
904✔
507
        local nx, ny, nz = normal:unpack()
311✔
508
        local d = -position:dot(normal)
314✔
509
        tmp[1] = 1 - 2 * nx ^ 2
311✔
510
        tmp[2] = 2 * nx * ny
309✔
511
        tmp[3] = -2 * nx * nz
309✔
512
        tmp[4] = 0
309✔
513
        tmp[5] = -2 * nx * ny
309✔
514
        tmp[6] = 1 - 2 * ny ^ 2
309✔
515
        tmp[7] = -2 * ny * nz
309✔
516
        tmp[8] = 0
309✔
517
        tmp[9] = -2 * nx * nz
309✔
518
        tmp[10] = -2 * ny * nz
309✔
519
        tmp[11] = 1 - 2 * nz ^ 2
309✔
520
        tmp[12] = 0
309✔
521
        tmp[13] = -2 * nx * d
309✔
522
        tmp[14] = -2 * ny * d
309✔
523
        tmp[15] = -2 * nz * d
309✔
524
        tmp[16] = 1
308✔
525

8✔
526
        return out:mul(tmp, a)
305✔
527
end
4✔
528

3✔
529
--- Transform matrix to look at a point.
1✔
530
-- @tparam mat4 out Matrix to store result
531
-- @tparam mat4 a Matrix to transform
532
-- @tparam vec3 eye Location of viewer's view plane
533
-- @tparam vec3 center Location of object to view
534
-- @tparam vec3 up Up direction
535
-- @treturn mat4 out
3✔
536
function mat4.look_at(out, a, eye, look_at, up)
904✔
537
        local z_axis = (eye - look_at):normalize()
311✔
538
        local x_axis = up:cross(z_axis):normalize()
314✔
539
        local y_axis = z_axis:cross(x_axis)
311✔
540
        out[1] = x_axis.x
309✔
541
        out[2] = y_axis.x
309✔
542
        out[3] = z_axis.x
309✔
543
        out[4] = 0
309✔
544
        out[5] = x_axis.y
309✔
545
        out[6] = y_axis.y
309✔
546
        out[7] = z_axis.y
309✔
547
        out[8] = 0
309✔
548
        out[9] = x_axis.z
309✔
549
        out[10] = y_axis.z
309✔
550
        out[11] = z_axis.z
309✔
551
        out[12] = 0
309✔
552
        out[13] = -out[  1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
309✔
553
        out[14] = -out[  2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
309✔
554
        out[15] = -out[  3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
309✔
555
        out[16] = -out[  4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
308✔
556

8✔
557
  return out
305✔
558
end
4✔
559

3✔
560
--- Transpose a matrix.
1✔
561
-- @tparam mat4 out Matrix to store the result
562
-- @tparam mat4 a Matrix to transpose
563
-- @treturn mat4 out
3✔
564
function mat4.transpose(out, a)
908✔
565
        tm4[1]  = a[1]
1,519✔
566
        tm4[2]  = a[5]
1,534✔
567
        tm4[3]  = a[9]
1,543✔
568
        tm4[4]  = a[13]
1,545✔
569
        tm4[5]  = a[2]
1,545✔
570
        tm4[6]  = a[6]
1,545✔
571
        tm4[7]  = a[10]
1,545✔
572
        tm4[8]  = a[14]
1,545✔
573
        tm4[9]  = a[3]
1,545✔
574
        tm4[10] = a[7]
1,545✔
575
        tm4[11] = a[11]
1,545✔
576
        tm4[12] = a[15]
1,545✔
577
        tm4[13] = a[4]
1,545✔
578
        tm4[14] = a[8]
1,545✔
579
        tm4[15] = a[12]
1,545✔
580
        tm4[16] = a[16]
1,540✔
581

120✔
582
        for i=1, 16 do
25,685✔
583
                out[i] = tm4[i]
24,340✔
584
        end
495✔
585

330✔
586
        return out
1,585✔
587
end
15✔
588

15✔
589
-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L518
5✔
590
--- Project a matrix from world space to screen space.
591
-- @tparam vec3 obj Object position in world space
592
-- @tparam mat4 view View matrix
593
-- @tparam mat4 projection Projection matrix
594
-- @tparam table viewport XYWH of viewport
595
-- @treturn vec3 win
3✔
596
function mat4.project(obj, view, projection, viewport)
905✔
597
        local position = { obj.x, obj.y, obj.z, 1 }
611✔
598

17✔
599
        mat4.mul_vec4(position, view,       position)
613✔
600
        mat4.mul_vec4(position, projection, position)
610✔
601

14✔
602
        position[1] = position[1] / position[4] * 0.5 + 0.5
612✔
603
        position[2] = position[2] / position[4] * 0.5 + 0.5
612✔
604
        position[3] = position[3] / position[4] * 0.5 + 0.5
614✔
605

16✔
606
        position[1] = position[1] * viewport[3] + viewport[1]
612✔
607
        position[2] = position[2] * viewport[4] + viewport[2]
610✔
608

14✔
609
        return vec3(position[1], position[2], position[3])
610✔
610
end
8✔
611

6✔
612
-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L544
2✔
613
--- Unproject a matrix from screen space to world space.
614
-- @tparam vec3 win Object position in screen space
615
-- @tparam mat4 view View matrix
616
-- @tparam mat4 projection Projection matrix
617
-- @tparam table viewport XYWH of viewport
618
-- @treturn vec3 obj
3✔
619
function mat4.unproject(win, view, projection, viewport)
904✔
620
        local position = { win.x, win.y, win.z, 1 }
310✔
621

13✔
622
        position[1] = (position[1] - viewport[1]) / viewport[3]
308✔
623
        position[2] = (position[2] - viewport[2]) / viewport[4]
305✔
624

7✔
625
        position[1] = position[1] * 2 - 1
306✔
626
        position[2] = position[2] * 2 - 1
306✔
627
        position[3] = position[3] * 2 - 1
307✔
628

8✔
629
        tmp:mul(projection, view):invert(tmp)
306✔
630
        mat4.mul_vec4(position, tmp, position)
305✔
631

7✔
632
        position[1] = position[1] / position[4]
306✔
633
        position[2] = position[2] / position[4]
306✔
634
        position[3] = position[3] / position[4]
307✔
635

8✔
636
        return vec3(position[1], position[2], position[3])
305✔
637
end
4✔
638

3✔
639
--- Return a boolean showing if a table is or is not a mat4.
1✔
640
-- @tparam mat4 a Matrix to be tested
641
-- @treturn boolean is_mat4
3✔
642
function mat4.is_mat4(a)
947✔
643
        if type(a) == "cdata" then
13,253✔
UNCOV
644
                return ffi.istype("cpml_mat4", a)
141✔
645
        end
135✔
646

88✔
647
        if type(a) ~= "table" then
13,245✔
648
                return false
433✔
649
        end
135✔
650

642✔
651
        for i = 1, 16 do
179,659✔
652
                if type(a[i]) ~= "number" then
171,259✔
653
                        return false
6,785✔
654
                end
2,317✔
655
        end
596✔
656

43✔
657
        return true
9,632✔
658
end
96✔
659

96✔
660
--- Return whether any component is NaN
32✔
661
-- @tparam mat4 a Matrix to be tested
662
-- @treturn boolean if any component is NaN
3✔
663
function vec2.has_nan(a)
903✔
UNCOV
664
        for i=1, 16 do
9✔
UNCOV
665
                if private.is_nan(a[i]) then
9✔
UNCOV
666
                        return true
3✔
667
                end
668
        end
UNCOV
669
        return false
670
end
671

672
--- Return a formatted string.
673
-- @tparam mat4 a Matrix to be turned into a string
674
-- @treturn string formatted
3✔
675
function mat4.to_string(a)
904✔
676
        local str = "[ "
327✔
677
        for i = 1, 16 do
5,145✔
678
                str = str .. string.format("%+0.3f", a[i])
4,889✔
679
                if i < 16 then
4,931✔
680
                        str = str .. ", "
4,628✔
681
                end
109✔
682
        end
62✔
683
        str = str .. " ]"
317✔
684
        return str
304✔
685
end
6✔
686

4✔
687
--- Convert a matrix to row vec4s.
1✔
688
-- @tparam mat4 a Matrix to be converted
689
-- @treturn table vec4s
3✔
690
function mat4.to_vec4s(a)
904✔
691
        return {
311✔
692
                { a[1],  a[2],  a[3],  a[4]  },
314✔
693
                { a[5],  a[6],  a[7],  a[8]  },
311✔
694
                { a[9],  a[10], a[11], a[12] },
309✔
695
                { a[13], a[14], a[15], a[16] }
309✔
696
        }
308✔
697
end
7✔
698

4✔
699
--- Convert a matrix to col vec4s.
1✔
700
-- @tparam mat4 a Matrix to be converted
701
-- @treturn table vec4s
3✔
702
function mat4.to_vec4s_cols(a)
904✔
703
        return {
311✔
704
                { a[1], a[5], a[9],  a[13] },
314✔
705
                { a[2], a[6], a[10], a[14] },
311✔
706
                { a[3], a[7], a[11], a[15] },
309✔
707
                { a[4], a[8], a[12], a[16] }
309✔
708
        }
308✔
709
end
7✔
710

4✔
711
-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
1✔
712
--- Convert a matrix to a quaternion.
713
-- @tparam mat4 a Matrix to be converted
714
-- @treturn quat out
3✔
715
function mat4.to_quat(a)
904✔
716
        identity(tmp):transpose(a)
310✔
717

13✔
718
        local w     = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
308✔
719
        local scale = w * 4
307✔
720
        local q     = quat.new(
609✔
721
                tmp[10] - tmp[7] / scale,
312✔
722
                tmp[3]  - tmp[9] / scale,
312✔
723
                tmp[5]  - tmp[2] / scale,
309✔
724
                w
8✔
725
        )
305✔
726

5✔
727
        return q:normalize(q)
304✔
728
end
4✔
729

3✔
730
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
1✔
731
--- Convert a matrix to a frustum.
732
-- @tparam mat4 a Matrix to be converted (projection * view)
733
-- @tparam boolean infinite Infinite removes the far plane
734
-- @treturn frustum out
3✔
735
function mat4.to_frustum(a, infinite)
903✔
736
        local t
10✔
737
        local frustum = {}
310✔
738

6✔
739
        -- Extract the LEFT plane
4✔
740
        frustum.left   = {}
303✔
741
        frustum.left.a = a[4]  + a[1]
305✔
742
        frustum.left.b = a[8]  + a[5]
308✔
743
        frustum.left.c = a[12] + a[9]
309✔
744
        frustum.left.d = a[16] + a[13]
308✔
745

7✔
746
        -- Normalize the result
5✔
747
        t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
303✔
748
        frustum.left.a = frustum.left.a / t
305✔
749
        frustum.left.b = frustum.left.b / t
308✔
750
        frustum.left.c = frustum.left.c / t
309✔
751
        frustum.left.d = frustum.left.d / t
308✔
752

7✔
753
        -- Extract the RIGHT plane
5✔
754
        frustum.right   = {}
303✔
755
        frustum.right.a = a[4]  - a[1]
305✔
756
        frustum.right.b = a[8]  - a[5]
308✔
757
        frustum.right.c = a[12] - a[9]
309✔
758
        frustum.right.d = a[16] - a[13]
308✔
759

7✔
760
        -- Normalize the result
5✔
761
        t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
303✔
762
        frustum.right.a = frustum.right.a / t
305✔
763
        frustum.right.b = frustum.right.b / t
308✔
764
        frustum.right.c = frustum.right.c / t
309✔
765
        frustum.right.d = frustum.right.d / t
308✔
766

7✔
767
        -- Extract the BOTTOM plane
5✔
768
        frustum.bottom   = {}
303✔
769
        frustum.bottom.a = a[4]  + a[2]
305✔
770
        frustum.bottom.b = a[8]  + a[6]
308✔
771
        frustum.bottom.c = a[12] + a[10]
309✔
772
        frustum.bottom.d = a[16] + a[14]
308✔
773

7✔
774
        -- Normalize the result
5✔
775
        t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
303✔
776
        frustum.bottom.a = frustum.bottom.a / t
305✔
777
        frustum.bottom.b = frustum.bottom.b / t
308✔
778
        frustum.bottom.c = frustum.bottom.c / t
309✔
779
        frustum.bottom.d = frustum.bottom.d / t
308✔
780

7✔
781
        -- Extract the TOP plane
5✔
782
        frustum.top   = {}
303✔
783
        frustum.top.a = a[4]  - a[2]
305✔
784
        frustum.top.b = a[8]  - a[6]
308✔
785
        frustum.top.c = a[12] - a[10]
309✔
786
        frustum.top.d = a[16] - a[14]
308✔
787

7✔
788
        -- Normalize the result
5✔
789
        t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
303✔
790
        frustum.top.a = frustum.top.a / t
305✔
791
        frustum.top.b = frustum.top.b / t
308✔
792
        frustum.top.c = frustum.top.c / t
309✔
793
        frustum.top.d = frustum.top.d / t
308✔
794

7✔
795
        -- Extract the NEAR plane
5✔
796
        frustum.near   = {}
303✔
797
        frustum.near.a = a[4]  + a[3]
305✔
798
        frustum.near.b = a[8]  + a[7]
308✔
799
        frustum.near.c = a[12] + a[11]
309✔
800
        frustum.near.d = a[16] + a[15]
308✔
801

7✔
802
        -- Normalize the result
5✔
803
        t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
303✔
804
        frustum.near.a = frustum.near.a / t
305✔
805
        frustum.near.b = frustum.near.b / t
308✔
806
        frustum.near.c = frustum.near.c / t
309✔
807
        frustum.near.d = frustum.near.d / t
308✔
808

8✔
809
        if not infinite then
305✔
810
                -- Extract the FAR plane
5✔
811
                frustum.far   = {}
305✔
812
                frustum.far.a = a[4]  - a[3]
306✔
813
                frustum.far.b = a[8]  - a[7]
308✔
814
                frustum.far.c = a[12] - a[11]
309✔
815
                frustum.far.d = a[16] - a[15]
308✔
816

7✔
817
                -- Normalize the result
5✔
818
                t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
303✔
819
                frustum.far.a = frustum.far.a / t
305✔
820
                frustum.far.b = frustum.far.b / t
308✔
821
                frustum.far.c = frustum.far.c / t
309✔
822
                frustum.far.d = frustum.far.d / t
308✔
823
        end
7✔
824

5✔
825
        return frustum
302✔
826
end
3✔
827

6✔
828
function mat4_mt.__index(t, k)
968✔
829
        if type(t) == "cdata" then
19,273✔
UNCOV
830
                if type(k) == "number" then
201✔
UNCOV
831
                        return t._m[k-1]
195✔
832
                end
64✔
833
        end
834

64✔
835
        return rawget(mat4, k)
19,264✔
836
end
192✔
837

195✔
838
function mat4_mt.__newindex(t, k, v)
964✔
UNCOV
839
        if type(t) == "cdata" then
12✔
UNCOV
840
                if type(k) == "number" then
9✔
UNCOV
841
                        t._m[k-1] = v
3✔
842
                end
843
        end
844
end
845

3✔
846
mat4_mt.__tostring = mat4.to_string
900✔
847

15✔
848
function mat4_mt.__call(_, a)
950✔
849
        return mat4.new(a)
12,315✔
850
end
173✔
851

129✔
852
function mat4_mt.__unm(a)
942✔
853
        return new():invert(a)
312✔
854
end
13✔
855

9✔
856
function mat4_mt.__eq(a, b)
904✔
857
        if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
912✔
UNCOV
858
                return false
21✔
859
        end
12✔
860

54✔
861
        for i = 1, 16 do
15,348✔
862
                if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
14,604✔
UNCOV
863
                        return false
345✔
864
                end
195✔
865
        end
48✔
866

3✔
867
        return true
900✔
868
end
12✔
869

12✔
870
function mat4_mt.__mul(a, b)
916✔
871
        precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))
3,912✔
872

74✔
873
        if vec3.is_vec3(b) then
3,944✔
874
                return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))
665✔
875
        end
47✔
876

30✔
877
        assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
3,302✔
878

55✔
879
        if mat4.is_mat4(b) then
3,339✔
880
                return new():mul(a, b)
1,855✔
881
        end
57✔
882

34✔
883
        return mat4.mul_vec4({}, a, b)
1,506✔
884
end
20✔
885

18✔
886
if status then
905✔
UNCOV
887
        xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
12✔
UNCOV
888
                ffi.metatype(new, mat4_mt)
9✔
UNCOV
889
        end, function() end)
3✔
890
end
3✔
891

3✔
892
return setmetatable({}, mat4_mt)
897✔
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