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

excessive / cpml / 1

21 Apr 2022 05:46PM UTC coverage: 54.321% (+0.7%) from 53.574%
1

push

github

web-flow
Merge pull request #76 from xiejiangzhi/xjz

Add Vec3.angle_to

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

130 existing lines in 7 files now uncovered.

5424 of 9985 relevant lines covered (54.32%)

290.75 hits per line

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

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

19
-- Private constructor.
20
local function new(m)
21
        m = m or {
420✔
22
                0, 0, 0, 0,
399✔
23
                0, 0, 0, 0,
399✔
24
                0, 0, 0, 0,
399✔
25
                0, 0, 0, 0
399✔
26
        }
399✔
27
        m._m = m
420✔
28
        return setmetatable(m, mat4_mt)
420✔
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
77✔
33
        m[5],  m[6],  m[7],  m[8]  = 0, 1, 0, 0
77✔
34
        m[9],  m[10], m[11], m[12] = 0, 0, 1, 0
77✔
35
        m[13], m[14], m[15], m[16] = 0, 0, 0, 1
77✔
36
        return m
77✔
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
21✔
42
   --  status, ffi = pcall(require, "ffi")
43
    if status then
×
UNCOV
44
        ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;"
×
UNCOV
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()
21✔
51
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
21✔
52
local tv4 = { 0, 0, 0, 0 }
21✔
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)
21✔
62
        local out = new()
287✔
63

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

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

77
        -- 4 vec4s
78
        elseif type(a) == "table" and type(a[1]) == "table" then
210✔
79
                local idx = 1
7✔
80
                for i = 1, 4 do
35✔
81
                        for j = 1, 4 do
140✔
82
                                out[idx] = a[i][j]
112✔
83
                                idx = idx + 1
112✔
84
                        end
85
                end
86

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

95
        return out
287✔
96
end
97

98
--- Create an identity matrix.
99
-- @tparam mat4 a Matrix to overwrite
100
-- @treturn mat4 out
101
function mat4.identity(a)
21✔
102
        return identity(a or new())
7✔
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)
21✔
110
        local l = axis:len()
×
111
        if l == 0 then
×
UNCOV
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)
×
UNCOV
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,
×
UNCOV
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)
21✔
UNCOV
131
        return mat4.from_angle_axis(q:to_angle_axis())
×
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)
21✔
139
        local forward = vec3.normalize(direction)
×
140
        local side = vec3.cross(forward, up):normalize()
×
UNCOV
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
×
UNCOV
153
        out[16]   = 1
×
154

UNCOV
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
163
function mat4.from_transform(trans, rot, scale)
21✔
164
        local rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
7✔
165

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

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

180
        local rsm = rm * sm
7✔
181

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

186
        return rsm
7✔
187
end
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
197
function mat4.from_ortho(left, right, top, bottom, near, far)
21✔
198
        local out = new()
×
UNCOV
199
        out[1]    =  2 / (right - left)
×
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))
×
UNCOV
204
        out[15]   = -((far + near) / (far - near))
×
UNCOV
205
        out[16]   =  1
×
206

UNCOV
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
216
function mat4.from_perspective(fovy, aspect, near, far)
21✔
217
        assert(aspect ~= 0)
28✔
218
        assert(near   ~= far)
28✔
219

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

229
        return out
28✔
230
end
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
240
function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
21✔
241
        -- CPML is right-handed and intended for GL, so these don't need to be arguments.
242
        local rightHanded = true
7✔
243
        local isOpenGL    = true
7✔
244

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

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

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.
259
                 return result
7✔
260
        end
261

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

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

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

278
        -- Produces Y result, mapping clip edges to [-w,+w]
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.
282
        projection[5] = 0
7✔
283
        projection[6] = scaleAndOffset.Scale.y
7✔
284
        projection[7] = handednessScale * -scaleAndOffset.Offset.y
7✔
285
        projection[8] = 0
7✔
286

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

292
        if farAtInfinity then
7✔
UNCOV
293
                if isOpenGL then
×
294
                        -- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
UNCOV
295
                        projection[11] = -handednessScale
×
UNCOV
296
                        projection[12] = 2.0 * zNear
×
297
                else
UNCOV
298
                        projection[11] = 0
×
UNCOV
299
                        projection[12] = zNear
×
300
                end
301
        else
302
                if isOpenGL then
7✔
303
                        -- Clip range is [-w,+w], so 0 is at the middle of the range.
304
                        projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
7✔
305
                        projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
7✔
306
                else
307
                        -- Clip range is [0,+w], so 0 is at the start of the range.
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)
314
        projection[13] = 0
7✔
315
        projection[14] = 0
7✔
316
        projection[15] = handednessScale
7✔
317
        projection[16] = 0
7✔
318

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

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

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"
334
function mat4.mul(out, a, b)
21✔
335
        tm4[1]  = b[1]  * a[1] + b[2]  * a[5] + b[3]  * a[9]  + b[4]  * a[13]
133✔
336
        tm4[2]  = b[1]  * a[2] + b[2]  * a[6] + b[3]  * a[10] + b[4]  * a[14]
133✔
337
        tm4[3]  = b[1]  * a[3] + b[2]  * a[7] + b[3]  * a[11] + b[4]  * a[15]
133✔
338
        tm4[4]  = b[1]  * a[4] + b[2]  * a[8] + b[3]  * a[12] + b[4]  * a[16]
133✔
339
        tm4[5]  = b[5]  * a[1] + b[6]  * a[5] + b[7]  * a[9]  + b[8]  * a[13]
133✔
340
        tm4[6]  = b[5]  * a[2] + b[6]  * a[6] + b[7]  * a[10] + b[8]  * a[14]
133✔
341
        tm4[7]  = b[5]  * a[3] + b[6]  * a[7] + b[7]  * a[11] + b[8]  * a[15]
133✔
342
        tm4[8]  = b[5]  * a[4] + b[6]  * a[8] + b[7]  * a[12] + b[8]  * a[16]
133✔
343
        tm4[9]  = b[9]  * a[1] + b[10] * a[5] + b[11] * a[9]  + b[12] * a[13]
133✔
344
        tm4[10] = b[9]  * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
133✔
345
        tm4[11] = b[9]  * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
133✔
346
        tm4[12] = b[9]  * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
133✔
347
        tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9]  + b[16] * a[13]
133✔
348
        tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
133✔
349
        tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
133✔
350
        tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
133✔
351

352
        for i=1, 16 do
2,261✔
353
                out[i] = tm4[i]
2,128✔
354
        end
355

356
        return out
133✔
357
end
358

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
364
function mat4.mul_vec4(out, a, b)
21✔
365
        tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9]  + b[4] * a[13]
91✔
366
        tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
91✔
367
        tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
91✔
368
        tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
91✔
369

370
        for i=1, 4 do
455✔
371
                out[i] = tv4[i]
364✔
372
        end
373

374
        return out
91✔
375
end
376

377
--- Invert a matrix.
378
-- @tparam mat4 out Matrix to store the result
379
-- @tparam mat4 a Matrix to invert
380
-- @treturn mat4 out
381
function mat4.invert(out, a)
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
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]
21✔
398

399
        for i=1, 16 do
357✔
400
                out[i] = tm4[i]
336✔
401
        end
402

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

405
        if det == 0 then return a end
21✔
406

407
        det = 1 / det
21✔
408

409
        for i = 1, 16 do
357✔
410
                out[i] = out[i] * det
336✔
411
        end
412

413
        return out
21✔
414
end
415

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
421
function mat4.scale(out, a, s)
21✔
422
        identity(tmp)
7✔
423
        tmp[1]  = s.x
7✔
424
        tmp[6]  = s.y
7✔
425
        tmp[11] = s.z
7✔
426

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

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
436
function mat4.rotate(out, a, angle, axis)
21✔
437
        if type(angle) == "table" or type(angle) == "cdata" then
28✔
UNCOV
438
                angle, axis = angle:to_angle_axis()
×
439
        end
440

441
        local l = axis:len()
28✔
442

443
        if l == 0 then
28✔
UNCOV
444
                return a
×
445
        end
446

447
        local x, y, z = axis.x / l, axis.y / l, axis.z / l
28✔
448
        local c = cos(angle)
28✔
449
        local s = sin(angle)
28✔
450

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

462
        return out:mul(tmp, a)
28✔
463
end
464

465
--- Translate a matrix.
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
470
function mat4.translate(out, a, t)
21✔
471
        identity(tmp)
21✔
472
        tmp[13] = t.x
21✔
473
        tmp[14] = t.y
21✔
474
        tmp[15] = t.z
21✔
475

476
        return out:mul(tmp, a)
21✔
477
end
478

479
--- Shear a matrix.
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
489
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
21✔
490
        identity(tmp)
7✔
491
        tmp[2]  = yx or 0
7✔
492
        tmp[3]  = zx or 0
7✔
493
        tmp[5]  = xy or 0
7✔
494
        tmp[7]  = zy or 0
7✔
495
        tmp[9]  = xz or 0
7✔
496
        tmp[10] = yz or 0
7✔
497

498
        return out:mul(tmp, a)
7✔
499
end
500

501
--- Reflect a matrix across a plane.
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
506
function mat4.reflect(out, a, position, normal)
21✔
507
        local nx, ny, nz = normal:unpack()
7✔
508
        local d = -position:dot(normal)
7✔
509
        tmp[1] = 1 - 2 * nx ^ 2
7✔
510
        tmp[2] = 2 * nx * ny
7✔
511
        tmp[3] = -2 * nx * nz
7✔
512
        tmp[4] = 0
7✔
513
        tmp[5] = -2 * nx * ny
7✔
514
        tmp[6] = 1 - 2 * ny ^ 2
7✔
515
        tmp[7] = -2 * ny * nz
7✔
516
        tmp[8] = 0
7✔
517
        tmp[9] = -2 * nx * nz
7✔
518
        tmp[10] = -2 * ny * nz
7✔
519
        tmp[11] = 1 - 2 * nz ^ 2
7✔
520
        tmp[12] = 0
7✔
521
        tmp[13] = -2 * nx * d
7✔
522
        tmp[14] = -2 * ny * d
7✔
523
        tmp[15] = -2 * nz * d
7✔
524
        tmp[16] = 1
7✔
525

526
        return out:mul(tmp, a)
7✔
527
end
528

529
--- Transform matrix to look at a point.
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
536
function mat4.look_at(out, a, eye, look_at, up)
21✔
537
        local z_axis = (eye - look_at):normalize()
7✔
538
        local x_axis = up:cross(z_axis):normalize()
7✔
539
        local y_axis = z_axis:cross(x_axis)
7✔
540
        out[1] = x_axis.x
7✔
541
        out[2] = y_axis.x
7✔
542
        out[3] = z_axis.x
7✔
543
        out[4] = 0
7✔
544
        out[5] = x_axis.y
7✔
545
        out[6] = y_axis.y
7✔
546
        out[7] = z_axis.y
7✔
547
        out[8] = 0
7✔
548
        out[9] = x_axis.z
7✔
549
        out[10] = y_axis.z
7✔
550
        out[11] = z_axis.z
7✔
551
        out[12] = 0
7✔
552
        out[13] = -out[  1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
7✔
553
        out[14] = -out[  2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
7✔
554
        out[15] = -out[  3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
7✔
555
        out[16] = -out[  4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
7✔
556

557
  return out
7✔
558
end
559

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

582
        for i=1, 16 do
595✔
583
                out[i] = tm4[i]
560✔
584
        end
585

586
        return out
35✔
587
end
588

589
-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L518
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
596
function mat4.project(obj, view, projection, viewport)
21✔
597
        local position = { obj.x, obj.y, obj.z, 1 }
14✔
598

599
        mat4.mul_vec4(position, view,       position)
14✔
600
        mat4.mul_vec4(position, projection, position)
14✔
601

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

606
        position[1] = position[1] * viewport[3] + viewport[1]
14✔
607
        position[2] = position[2] * viewport[4] + viewport[2]
14✔
608

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

612
-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L544
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
619
function mat4.unproject(win, view, projection, viewport)
21✔
620
        local position = { win.x, win.y, win.z, 1 }
7✔
621

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

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

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

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

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

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

647
        if type(a) ~= "table" then
308✔
648
                return false
7✔
649
        end
650

651
        for i = 1, 16 do
4,165✔
652
                if type(a[i]) ~= "number" then
3,941✔
653
                        return false
77✔
654
                end
655
        end
656

657
        return true
224✔
658
end
659

660
--- Return whether any component is NaN
661
-- @tparam mat4 a Matrix to be tested
662
-- @treturn boolean if any component is NaN
663
function vec2.has_nan(a)
21✔
UNCOV
664
        for i=1, 16 do
×
UNCOV
665
                if private.is_nan(a[i]) then
×
UNCOV
666
                        return true
×
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
675
function mat4.to_string(a)
21✔
676
        local str = "[ "
7✔
677
        for i = 1, 16 do
119✔
678
                str = str .. string.format("%+0.3f", a[i])
112✔
679
                if i < 16 then
112✔
680
                        str = str .. ", "
105✔
681
                end
682
        end
683
        str = str .. " ]"
7✔
684
        return str
7✔
685
end
686

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

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

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

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

727
        return q:normalize(q)
7✔
728
end
729

730
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
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
735
function mat4.to_frustum(a, infinite)
21✔
736
        local t
737
        local frustum = {}
7✔
738

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

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

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

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

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

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

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

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

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

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

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

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

825
        return frustum
7✔
826
end
827

828
function mat4_mt.__index(t, k)
21✔
829
        if type(t) == "cdata" then
448✔
UNCOV
830
                if type(k) == "number" then
×
UNCOV
831
                        return t._m[k-1]
×
832
                end
833
        end
834

835
        return rawget(mat4, k)
448✔
836
end
837

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

846
mat4_mt.__tostring = mat4.to_string
21✔
847

848
function mat4_mt.__call(_, a)
21✔
849
        return mat4.new(a)
287✔
850
end
851

852
function mat4_mt.__unm(a)
21✔
853
        return new():invert(a)
7✔
854
end
855

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

861
        for i = 1, 16 do
357✔
862
                if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
336✔
UNCOV
863
                        return false
×
864
                end
865
        end
866

867
        return true
21✔
868
end
869

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

873
        if vec3.is_vec3(b) then
91✔
874
                return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))
14✔
875
        end
876

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

879
        if mat4.is_mat4(b) then
77✔
880
                return new():mul(a, b)
42✔
881
        end
882

883
        return mat4.mul_vec4({}, a, b)
35✔
884
end
885

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

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