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

excessive / cpml / 1

23 Jun 2021 11:35PM UTC coverage: 53.574% (+9.4%) from 44.185%
1

push

github

web-flow
Merge pull request #62 from idbrii/actions-busted

Run tests on gh actions

4452 of 8310 relevant lines covered (53.57%)

91.18 hits per line

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

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

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

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

47
-- Statically allocate a temporary variable used in some of our functions.
48
local tmp = new()
6✔
49
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
6✔
50
local tv4 = { 0, 0, 0, 0 }
6✔
51
local forward, side, new_up = vec3(), vec3(), vec3()
6✔
52

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

63
        -- 4x4 matrix
64
        if type(a) == "table" and #a == 16 then
82✔
65
                for i = 1, 16 do
340✔
66
                        out[i] = tonumber(a[i])
320✔
67
                end
68

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

76
        -- 4 vec4s
77
        elseif type(a) == "table" and type(a[1]) == "table" then
60✔
78
                local idx = 1
2✔
79
                for i = 1, 4 do
10✔
80
                        for j = 1, 4 do
40✔
81
                                out[idx] = a[i][j]
32✔
82
                                idx = idx + 1
32✔
83
                        end
84
                end
85

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

94
        return out
82✔
95
end
96

97
--- Create an identity matrix.
98
-- @tparam mat4 a Matrix to overwrite
99
-- @treturn mat4 out
100
function mat4.identity(a)
6✔
101
        return identity(a or new())
2✔
102
end
103

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

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

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

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

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

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

154
        return out
×
155
end
156

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

166
        if l == 0 then
×
167
                return new()
×
168
        end
169

170
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
×
171
        local c = cos(angle)
×
172
        local s = sin(angle)
×
173

174
        return new {
×
175
                x*x*(1-c)+c,   y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
×
176
                x*y*(1-c)-z*s, y*y*(1-c)+c,   y*z*(1-c)+x*s, 0,
×
177
                x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c,   0,
×
178
                trans.x, trans.y, trans.z, 1
×
179
        }
180
end
181

182
--- Create matrix from orthogonal.
183
-- @tparam number left
184
-- @tparam number right
185
-- @tparam number top
186
-- @tparam number bottom
187
-- @tparam number near
188
-- @tparam number far
189
-- @treturn mat4 out
190
function mat4.from_ortho(left, right, top, bottom, near, far)
6✔
191
        local out = new()
×
192
        out[1]    =  2 / (right - left)
×
193
        out[6]    =  2 / (top - bottom)
×
194
        out[11]   = -2 / (far - near)
×
195
        out[13]   = -((right + left) / (right - left))
×
196
        out[14]   = -((top + bottom) / (top - bottom))
×
197
        out[15]   = -((far + near) / (far - near))
×
198
        out[16]   =  1
×
199

200
        return out
×
201
end
202

203
--- Create matrix from perspective.
204
-- @tparam number fovy Field of view
205
-- @tparam number aspect Aspect ratio
206
-- @tparam number near Near plane
207
-- @tparam number far Far plane
208
-- @treturn mat4 out
209
function mat4.from_perspective(fovy, aspect, near, far)
6✔
210
        assert(aspect ~= 0)
8✔
211
        assert(near   ~= far)
8✔
212

213
        local t   = tan(rad(fovy) / 2)
8✔
214
        local out = new()
8✔
215
        out[1]    =  1 / (t * aspect)
8✔
216
        out[6]    =  1 / t
8✔
217
        out[11]   = -(far + near) / (far - near)
8✔
218
        out[12]   = -1
8✔
219
        out[15]   = -(2 * far * near) / (far - near)
8✔
220
        out[16]   =  0
8✔
221

222
        return out
8✔
223
end
224

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

238
        local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
239
                x_scale  = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
2✔
240
                x_offset =     (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
2✔
241
                y_scale  = 2 / (tanHalfFov.UpTan   + tanHalfFov.DownTan )
2✔
242
                y_offset =     (tanHalfFov.UpTan   - tanHalfFov.DownTan ) * y_scale * 0.5
2✔
243

244
                local result = {
2✔
245
                        Scale  = vec2(x_scale, y_scale),
2✔
246
                        Offset = vec2(x_offset, y_offset)
2✔
247
                }
248

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

255
        if not flipZ and farAtInfinity then
2✔
256
                print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
×
257
                farAtInfinity = false
×
258
        end
259

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

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

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

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

285
        if farAtInfinity then
2✔
286
                if isOpenGL then
×
287
                        -- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
288
                        projection[11] = -handednessScale
×
289
                        projection[12] = 2.0 * zNear
×
290
                else
291
                        projection[11] = 0
×
292
                        projection[12] = zNear
×
293
                end
294
        else
295
                if isOpenGL then
2✔
296
                        -- Clip range is [-w,+w], so 0 is at the middle of the range.
297
                        projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
2✔
298
                        projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
2✔
299
                else
300
                        -- Clip range is [0,+w], so 0 is at the start of the range.
301
                        projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
×
302
                        projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
×
303
                end
304
        end
305

306
        -- Produces W result (= Z in)
307
        projection[13] = 0
2✔
308
        projection[14] = 0
2✔
309
        projection[15] = handednessScale
2✔
310
        projection[16] = 0
2✔
311

312
        return projection:transpose(projection)
2✔
313
end
314

315
--- Clone a matrix.
316
-- @tparam mat4 a Matrix to clone
317
-- @treturn mat4 out
318
function mat4.clone(a)
6✔
319
        return new(a)
2✔
320
end
321

322
--- Multiply two matrices.
323
-- @tparam mat4 out Matrix to store the result
324
-- @tparam mat4 a Left hand operand
325
-- @tparam mat4 b Right hand operand
326
-- @treturn mat4 out Matrix equivalent to "apply b, then a"
327
function mat4.mul(out, a, b)
6✔
328
        tm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]
36✔
329
        tm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]
36✔
330
        tm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]
36✔
331
        tm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]
36✔
332
        tm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]
36✔
333
        tm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]
36✔
334
        tm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]
36✔
335
        tm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]
36✔
336
        tm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]
36✔
337
        tm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
36✔
338
        tm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
36✔
339
        tm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
36✔
340
        tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]
36✔
341
        tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
36✔
342
        tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
36✔
343
        tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
36✔
344

345
        for i=1, 16 do
612✔
346
                out[i] = tm4[i]
576✔
347
        end
348

349
        return out
36✔
350
end
351

352
--- Multiply a matrix and a vec4.
353
-- @tparam mat4 out Matrix to store the result
354
-- @tparam mat4 a Left hand operand
355
-- @tparam table b Right hand operand
356
-- @treturn mat4 out
357
function mat4.mul_vec4(out, a, b)
6✔
358
        tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]
24✔
359
        tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
24✔
360
        tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
24✔
361
        tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
24✔
362

363
        for i=1, 4 do
120✔
364
                out[i] = tv4[i]
96✔
365
        end
366

367
        return out
24✔
368
end
369

370
--- Invert a matrix.
371
-- @tparam mat4 out Matrix to store the result
372
-- @tparam mat4 a Matrix to invert
373
-- @treturn mat4 out
374
function mat4.invert(out, a)
6✔
375
        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]
6✔
376
        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]
6✔
377
        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✔
378
        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]
6✔
379
        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]
6✔
380
        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]
6✔
381
        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]
6✔
382
        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]
6✔
383
        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]
6✔
384
        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]
6✔
385
        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]
6✔
386
        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]
6✔
387
        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]
6✔
388
        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]
6✔
389
        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]
6✔
390
        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]
6✔
391

392
        for i=1, 16 do
102✔
393
                out[i] = tm4[i]
96✔
394
        end
395

396
        local det = a[1] * out[1] + a[2] * out[5] + a[3] * out[9] + a[4] * out[13]
6✔
397

398
        if det == 0 then return a end
6✔
399

400
        det = 1 / det
6✔
401

402
        for i = 1, 16 do
102✔
403
                out[i] = out[i] * det
96✔
404
        end
405

406
        return out
6✔
407
end
408

409
--- Scale a matrix.
410
-- @tparam mat4 out Matrix to store the result
411
-- @tparam mat4 a Matrix to scale
412
-- @tparam vec3 s Scalar
413
-- @treturn mat4 out
414
function mat4.scale(out, a, s)
6✔
415
        identity(tmp)
2✔
416
        tmp[1]  = s.x
2✔
417
        tmp[6]  = s.y
2✔
418
        tmp[11] = s.z
2✔
419

420
        return out:mul(tmp, a)
2✔
421
end
422

423
--- Rotate a matrix.
424
-- @tparam mat4 out Matrix to store the result
425
-- @tparam mat4 a Matrix to rotate
426
-- @tparam number angle Angle to rotate by (in radians)
427
-- @tparam vec3 axis Axis to rotate on
428
-- @treturn mat4 out
429
function mat4.rotate(out, a, angle, axis)
6✔
430
        if type(angle) == "table" or type(angle) == "cdata" then
8✔
431
                angle, axis = angle:to_angle_axis()
×
432
        end
433

434
        local l = axis:len()
8✔
435

436
        if l == 0 then
8✔
437
                return a
×
438
        end
439

440
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
8✔
441
        local c = cos(angle)
8✔
442
        local s = sin(angle)
8✔
443

444
        identity(tmp)
8✔
445
        tmp[1]  = x * x * (1 - c) + c
8✔
446
        tmp[2]  = y * x * (1 - c) + z * s
8✔
447
        tmp[3]  = x * z * (1 - c) - y * s
8✔
448
        tmp[5]  = x * y * (1 - c) - z * s
8✔
449
        tmp[6]  = y * y * (1 - c) + c
8✔
450
         tmp[7]  = y * z * (1 - c) + x * s
8✔
451
        tmp[9]  = x * z * (1 - c) + y * s
8✔
452
        tmp[10] = y * z * (1 - c) - x * s
8✔
453
        tmp[11] = z * z * (1 - c) + c
8✔
454

455
        return out:mul(tmp, a)
8✔
456
end
457

458
--- Translate a matrix.
459
-- @tparam mat4 out Matrix to store the result
460
-- @tparam mat4 a Matrix to translate
461
-- @tparam vec3 t Translation vector
462
-- @treturn mat4 out
463
function mat4.translate(out, a, t)
6✔
464
        identity(tmp)
6✔
465
        tmp[13] = t.x
6✔
466
        tmp[14] = t.y
6✔
467
        tmp[15] = t.z
6✔
468

469
        return out:mul(tmp, a)
6✔
470
end
471

472
--- Shear a matrix.
473
-- @tparam mat4 out Matrix to store the result
474
-- @tparam mat4 a Matrix to translate
475
-- @tparam number yx
476
-- @tparam number zx
477
-- @tparam number xy
478
-- @tparam number zy
479
-- @tparam number xz
480
-- @tparam number yz
481
-- @treturn mat4 out
482
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
6✔
483
        identity(tmp)
2✔
484
        tmp[2]  = yx or 0
2✔
485
        tmp[3]  = zx or 0
2✔
486
        tmp[5]  = xy or 0
2✔
487
        tmp[7]  = zy or 0
2✔
488
        tmp[9]  = xz or 0
2✔
489
        tmp[10] = yz or 0
2✔
490

491
        return out:mul(tmp, a)
2✔
492
end
493

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

519
        return out:mul(tmp, a)
2✔
520
end
521

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

550
  return out
2✔
551
end
552

553
--- Transpose a matrix.
554
-- @tparam mat4 out Matrix to store the result
555
-- @tparam mat4 a Matrix to transpose
556
-- @treturn mat4 out
557
function mat4.transpose(out, a)
6✔
558
        tm4[1]  = a[1]
10✔
559
        tm4[2]  = a[5]
10✔
560
        tm4[3]  = a[9]
10✔
561
        tm4[4]  = a[13]
10✔
562
        tm4[5]  = a[2]
10✔
563
        tm4[6]  = a[6]
10✔
564
        tm4[7]  = a[10]
10✔
565
        tm4[8]  = a[14]
10✔
566
        tm4[9]  = a[3]
10✔
567
        tm4[10] = a[7]
10✔
568
        tm4[11] = a[11]
10✔
569
        tm4[12] = a[15]
10✔
570
        tm4[13] = a[4]
10✔
571
        tm4[14] = a[8]
10✔
572
        tm4[15] = a[12]
10✔
573
        tm4[16] = a[16]
10✔
574

575
        for i=1, 16 do
170✔
576
                out[i] = tm4[i]
160✔
577
        end
578

579
        return out
10✔
580
end
581

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

592
        mat4.mul_vec4(position, view,       position)
4✔
593
        mat4.mul_vec4(position, projection, position)
4✔
594

595
        position[1] = position[1] / position[4] * 0.5 + 0.5
4✔
596
        position[2] = position[2] / position[4] * 0.5 + 0.5
4✔
597
        position[3] = position[3] / position[4] * 0.5 + 0.5
4✔
598

599
        position[1] = position[1] * viewport[3] + viewport[1]
4✔
600
        position[2] = position[2] * viewport[4] + viewport[2]
4✔
601

602
        return vec3(position[1], position[2], position[3])
4✔
603
end
604

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

615
        position[1] = (position[1] - viewport[1]) / viewport[3]
2✔
616
        position[2] = (position[2] - viewport[2]) / viewport[4]
2✔
617

618
        position[1] = position[1] * 2 - 1
2✔
619
        position[2] = position[2] * 2 - 1
2✔
620
        position[3] = position[3] * 2 - 1
2✔
621

622
        tmp:mul(projection, view):invert(tmp)
2✔
623
        mat4.mul_vec4(position, tmp, position)
2✔
624

625
        position[1] = position[1] / position[4]
2✔
626
        position[2] = position[2] / position[4]
2✔
627
        position[3] = position[3] / position[4]
2✔
628

629
        return vec3(position[1], position[2], position[3])
2✔
630
end
631

632
--- Return a boolean showing if a table is or is not a mat4.
633
-- @tparam mat4 a Matrix to be tested
634
-- @treturn boolean is_mat4
635
function mat4.is_mat4(a)
6✔
636
        if type(a) == "cdata" then
80✔
637
                return ffi.istype("cpml_mat4", a)
×
638
        end
639

640
        if type(a) ~= "table" then
80✔
641
                return false
2✔
642
        end
643

644
        for i = 1, 16 do
1,054✔
645
                if type(a[i]) ~= "number" then
998✔
646
                        return false
22✔
647
                end
648
        end
649

650
        return true
56✔
651
end
652

653
--- Return a formatted string.
654
-- @tparam mat4 a Matrix to be turned into a string
655
-- @treturn string formatted
656
function mat4.to_string(a)
6✔
657
        local str = "[ "
2✔
658
        for i = 1, 16 do
34✔
659
                str = str .. string.format("%+0.3f", a[i])
32✔
660
                if i < 16 then
32✔
661
                        str = str .. ", "
30✔
662
                end
663
        end
664
        str = str .. " ]"
2✔
665
        return str
2✔
666
end
667

668
--- Convert a matrix to row vec4s.
669
-- @tparam mat4 a Matrix to be converted
670
-- @treturn table vec4s
671
function mat4.to_vec4s(a)
6✔
672
        return {
2✔
673
                { a[1],  a[2],  a[3],  a[4]  },
2✔
674
                { a[5],  a[6],  a[7],  a[8]  },
2✔
675
                { a[9],  a[10], a[11], a[12] },
2✔
676
                { a[13], a[14], a[15], a[16] }
2✔
677
        }
2✔
678
end
679

680
--- Convert a matrix to col vec4s.
681
-- @tparam mat4 a Matrix to be converted
682
-- @treturn table vec4s
683
function mat4.to_vec4s_cols(a)
6✔
684
        return {
2✔
685
                { a[1], a[5], a[9],  a[13] },
2✔
686
                { a[2], a[6], a[10], a[14] },
2✔
687
                { a[3], a[7], a[11], a[15] },
2✔
688
                { a[4], a[8], a[12], a[16] }
2✔
689
        }
2✔
690
end
691

692
-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
693
--- Convert a matrix to a quaternion.
694
-- @tparam mat4 a Matrix to be converted
695
-- @treturn quat out
696
function mat4.to_quat(a)
6✔
697
        identity(tmp):transpose(a)
2✔
698

699
        local w     = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
2✔
700
        local scale = w * 4
2✔
701
        local q     = quat.new(
4✔
702
                tmp[10] - tmp[7] / scale,
2✔
703
                tmp[3]  - tmp[9] / scale,
2✔
704
                tmp[5]  - tmp[2] / scale,
2✔
705
                w
706
        )
2✔
707

708
        return q:normalize(q)
2✔
709
end
710

711
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
712
--- Convert a matrix to a frustum.
713
-- @tparam mat4 a Matrix to be converted (projection * view)
714
-- @tparam boolean infinite Infinite removes the far plane
715
-- @treturn frustum out
716
function mat4.to_frustum(a, infinite)
6✔
717
        local t
718
        local frustum = {}
2✔
719

720
        -- Extract the LEFT plane
721
        frustum.left   = {}
2✔
722
        frustum.left.a = a[4]  + a[1]
2✔
723
        frustum.left.b = a[8]  + a[5]
2✔
724
        frustum.left.c = a[12] + a[9]
2✔
725
        frustum.left.d = a[16] + a[13]
2✔
726

727
        -- Normalize the result
728
        t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
2✔
729
        frustum.left.a = frustum.left.a / t
2✔
730
        frustum.left.b = frustum.left.b / t
2✔
731
        frustum.left.c = frustum.left.c / t
2✔
732
        frustum.left.d = frustum.left.d / t
2✔
733

734
        -- Extract the RIGHT plane
735
        frustum.right   = {}
2✔
736
        frustum.right.a = a[4]  - a[1]
2✔
737
        frustum.right.b = a[8]  - a[5]
2✔
738
        frustum.right.c = a[12] - a[9]
2✔
739
        frustum.right.d = a[16] - a[13]
2✔
740

741
        -- Normalize the result
742
        t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
2✔
743
        frustum.right.a = frustum.right.a / t
2✔
744
        frustum.right.b = frustum.right.b / t
2✔
745
        frustum.right.c = frustum.right.c / t
2✔
746
        frustum.right.d = frustum.right.d / t
2✔
747

748
        -- Extract the BOTTOM plane
749
        frustum.bottom   = {}
2✔
750
        frustum.bottom.a = a[4]  + a[2]
2✔
751
        frustum.bottom.b = a[8]  + a[6]
2✔
752
        frustum.bottom.c = a[12] + a[10]
2✔
753
        frustum.bottom.d = a[16] + a[14]
2✔
754

755
        -- Normalize the result
756
        t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
2✔
757
        frustum.bottom.a = frustum.bottom.a / t
2✔
758
        frustum.bottom.b = frustum.bottom.b / t
2✔
759
        frustum.bottom.c = frustum.bottom.c / t
2✔
760
        frustum.bottom.d = frustum.bottom.d / t
2✔
761

762
        -- Extract the TOP plane
763
        frustum.top   = {}
2✔
764
        frustum.top.a = a[4]  - a[2]
2✔
765
        frustum.top.b = a[8]  - a[6]
2✔
766
        frustum.top.c = a[12] - a[10]
2✔
767
        frustum.top.d = a[16] - a[14]
2✔
768

769
        -- Normalize the result
770
        t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
2✔
771
        frustum.top.a = frustum.top.a / t
2✔
772
        frustum.top.b = frustum.top.b / t
2✔
773
        frustum.top.c = frustum.top.c / t
2✔
774
        frustum.top.d = frustum.top.d / t
2✔
775

776
        -- Extract the NEAR plane
777
        frustum.near   = {}
2✔
778
        frustum.near.a = a[4]  + a[3]
2✔
779
        frustum.near.b = a[8]  + a[7]
2✔
780
        frustum.near.c = a[12] + a[11]
2✔
781
        frustum.near.d = a[16] + a[15]
2✔
782

783
        -- Normalize the result
784
        t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
2✔
785
        frustum.near.a = frustum.near.a / t
2✔
786
        frustum.near.b = frustum.near.b / t
2✔
787
        frustum.near.c = frustum.near.c / t
2✔
788
        frustum.near.d = frustum.near.d / t
2✔
789

790
        if not infinite then
2✔
791
                -- Extract the FAR plane
792
                frustum.far   = {}
2✔
793
                frustum.far.a = a[4]  - a[3]
2✔
794
                frustum.far.b = a[8]  - a[7]
2✔
795
                frustum.far.c = a[12] - a[11]
2✔
796
                frustum.far.d = a[16] - a[15]
2✔
797

798
                -- Normalize the result
799
                t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
2✔
800
                frustum.far.a = frustum.far.a / t
2✔
801
                frustum.far.b = frustum.far.b / t
2✔
802
                frustum.far.c = frustum.far.c / t
2✔
803
                frustum.far.d = frustum.far.d / t
2✔
804
        end
805

806
        return frustum
2✔
807
end
808

809
function mat4_mt.__index(t, k)
6✔
810
        if type(t) == "cdata" then
122✔
811
                if type(k) == "number" then
×
812
                        return t._m[k-1]
×
813
                end
814
        end
815

816
        return rawget(mat4, k)
122✔
817
end
818

819
function mat4_mt.__newindex(t, k, v)
6✔
820
        if type(t) == "cdata" then
×
821
                if type(k) == "number" then
×
822
                        t._m[k-1] = v
×
823
                end
824
        end
825
end
826

827
mat4_mt.__tostring = mat4.to_string
6✔
828

829
function mat4_mt.__call(_, a)
6✔
830
        return mat4.new(a)
82✔
831
end
832

833
function mat4_mt.__unm(a)
6✔
834
        return new():invert(a)
2✔
835
end
836

837
function mat4_mt.__eq(a, b)
6✔
838
        if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
6✔
839
                return false
×
840
        end
841

842
        for i = 1, 16 do
102✔
843
                if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
96✔
844
                        return false
×
845
                end
846
        end
847

848
        return true
6✔
849
end
850

851
function mat4_mt.__mul(a, b)
6✔
852
        assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operand. (<cpml.mat4> expected)")
22✔
853

854
        if vec3.is_vec3(b) then
22✔
855
                return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))
2✔
856
        end
857

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

860
        if mat4.is_mat4(b) then
20✔
861
                return new():mul(a, b)
10✔
862
        end
863

864
        return mat4.mul_vec4({}, a, b)
10✔
865
end
866

867
if status then
6✔
868
        xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
×
869
                ffi.metatype(new, mat4_mt)
×
870
        end, function() end)
×
871
end
872

873
return setmetatable({}, mat4_mt)
6✔
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