mat4.lua 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. --- double 4x4, 1-based, column major matrices
  2. -- @module mat4
  3. local modules = (...):gsub('%.[^%.]+$', '') .. "."
  4. local constants = require(modules .. "constants")
  5. local vec2 = require(modules .. "vec2")
  6. local vec3 = require(modules .. "vec3")
  7. local quat = require(modules .. "quat")
  8. local utils = require(modules .. "utils")
  9. local sqrt = math.sqrt
  10. local cos = math.cos
  11. local sin = math.sin
  12. local tan = math.tan
  13. local rad = math.rad
  14. local mat4 = {}
  15. local mat4_mt = {}
  16. -- Private constructor.
  17. local function new(m)
  18. m = m or {
  19. 0, 0, 0, 0,
  20. 0, 0, 0, 0,
  21. 0, 0, 0, 0,
  22. 0, 0, 0, 0
  23. }
  24. m._m = m
  25. return setmetatable(m, mat4_mt)
  26. end
  27. -- Convert matrix into identity
  28. local function identity(m)
  29. m[1], m[2], m[3], m[4] = 1, 0, 0, 0
  30. m[5], m[6], m[7], m[8] = 0, 1, 0, 0
  31. m[9], m[10], m[11], m[12] = 0, 0, 1, 0
  32. m[13], m[14], m[15], m[16] = 0, 0, 0, 1
  33. return m
  34. end
  35. -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
  36. local status, ffi, the_type
  37. if type(jit) == "table" and jit.status() then
  38. -- status, ffi = pcall(require, "ffi")
  39. if status then
  40. ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;"
  41. new = ffi.typeof("cpml_mat4")
  42. end
  43. end
  44. -- Statically allocate a temporary variable used in some of our functions.
  45. local tmp = new()
  46. local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
  47. local tv4 = { 0, 0, 0, 0 }
  48. local forward, side, new_up = vec3(), vec3(), vec3()
  49. --- The public constructor.
  50. -- @param a Can be of four types: </br>
  51. -- table Length 16 (4x4 matrix)
  52. -- table Length 9 (3x3 matrix)
  53. -- table Length 4 (4 vec4s)
  54. -- nil
  55. -- @treturn mat4 out
  56. function mat4.new(a)
  57. local out = new()
  58. -- 4x4 matrix
  59. if type(a) == "table" and #a == 16 then
  60. for i = 1, 16 do
  61. out[i] = tonumber(a[i])
  62. end
  63. -- 3x3 matrix
  64. elseif type(a) == "table" and #a == 9 then
  65. out[1], out[2], out[3] = a[1], a[2], a[3]
  66. out[5], out[6], out[7] = a[4], a[5], a[6]
  67. out[9], out[10], out[11] = a[7], a[8], a[9]
  68. out[16] = 1
  69. -- 4 vec4s
  70. elseif type(a) == "table" and type(a[1]) == "table" then
  71. local idx = 1
  72. for i = 1, 4 do
  73. for j = 1, 4 do
  74. out[idx] = a[i][j]
  75. idx = idx + 1
  76. end
  77. end
  78. -- nil
  79. else
  80. out[1] = 1
  81. out[6] = 1
  82. out[11] = 1
  83. out[16] = 1
  84. end
  85. return out
  86. end
  87. --- Create an identity matrix.
  88. -- @tparam mat4 a Matrix to overwrite
  89. -- @treturn mat4 out
  90. function mat4.identity(a)
  91. return identity(a or new())
  92. end
  93. --- Create a matrix from an angle/axis pair.
  94. -- @tparam number angle Angle of rotation
  95. -- @tparam vec3 axis Axis of rotation
  96. -- @treturn mat4 out
  97. function mat4.from_angle_axis(angle, axis)
  98. local l = axis:len()
  99. if l == 0 then
  100. return new()
  101. end
  102. local x, y, z = axis.x / l, axis.y / l, axis.z / l
  103. local c = cos(angle)
  104. local s = sin(angle)
  105. return new {
  106. x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
  107. x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0,
  108. x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0,
  109. 0, 0, 0, 1
  110. }
  111. end
  112. --- Create a matrix from a quaternion.
  113. -- @tparam quat q Rotation quaternion
  114. -- @treturn mat4 out
  115. function mat4.from_quaternion(q)
  116. return mat4.from_angle_axis(q:to_angle_axis())
  117. end
  118. --- Create a matrix from a direction/up pair.
  119. -- @tparam vec3 direction Vector direction
  120. -- @tparam vec3 up Up direction
  121. -- @treturn mat4 out
  122. function mat4.from_direction(direction, up)
  123. local forward = vec3.normalize(direction)
  124. local side = vec3.cross(forward, up):normalize()
  125. local new_up = vec3.cross(side, forward):normalize()
  126. local out = new()
  127. out[1] = side.x
  128. out[5] = side.y
  129. out[9] = side.z
  130. out[2] = new_up.x
  131. out[6] = new_up.y
  132. out[10] = new_up.z
  133. out[3] = forward.x
  134. out[7] = forward.y
  135. out[11] = forward.z
  136. out[16] = 1
  137. return out
  138. end
  139. --- Create a matrix from a transform.
  140. -- @tparam vec3 trans Translation vector
  141. -- @tparam quat rot Rotation quaternion
  142. -- @tparam vec3 scale Scale vector
  143. -- @treturn mat4 out
  144. function mat4.from_transform(trans, rot, scale)
  145. local angle, axis = rot:to_angle_axis()
  146. local l = axis:len()
  147. if l == 0 then
  148. return new()
  149. end
  150. local x, y, z = axis.x / l, axis.y / l, axis.z / l
  151. local c = cos(angle)
  152. local s = sin(angle)
  153. return new {
  154. x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
  155. x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0,
  156. x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0,
  157. trans.x, trans.y, trans.z, 1
  158. }
  159. end
  160. --- Create matrix from orthogonal.
  161. -- @tparam number left
  162. -- @tparam number right
  163. -- @tparam number top
  164. -- @tparam number bottom
  165. -- @tparam number near
  166. -- @tparam number far
  167. -- @treturn mat4 out
  168. function mat4.from_ortho(left, right, top, bottom, near, far)
  169. local out = new()
  170. out[1] = 2 / (right - left)
  171. out[6] = 2 / (top - bottom)
  172. out[11] = -2 / (far - near)
  173. out[13] = -((right + left) / (right - left))
  174. out[14] = -((top + bottom) / (top - bottom))
  175. out[15] = -((far + near) / (far - near))
  176. out[16] = 1
  177. return out
  178. end
  179. --- Create matrix from perspective.
  180. -- @tparam number fovy Field of view
  181. -- @tparam number aspect Aspect ratio
  182. -- @tparam number near Near plane
  183. -- @tparam number far Far plane
  184. -- @treturn mat4 out
  185. function mat4.from_perspective(fovy, aspect, near, far)
  186. assert(aspect ~= 0)
  187. assert(near ~= far)
  188. local t = tan(rad(fovy) / 2)
  189. local out = new()
  190. out[1] = 1 / (t * aspect)
  191. out[6] = 1 / t
  192. out[11] = -(far + near) / (far - near)
  193. out[12] = -1
  194. out[15] = -(2 * far * near) / (far - near)
  195. out[16] = 0
  196. return out
  197. end
  198. -- Adapted from the Oculus SDK.
  199. --- Create matrix from HMD perspective.
  200. -- @tparam number tanHalfFov Tangent of half of the field of view
  201. -- @tparam number zNear Near plane
  202. -- @tparam number zFar Far plane
  203. -- @tparam boolean flipZ Z axis is flipped or not
  204. -- @tparam boolean farAtInfinity Far plane is infinite or not
  205. -- @treturn mat4 out
  206. function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
  207. -- CPML is right-handed and intended for GL, so these don't need to be arguments.
  208. local rightHanded = true
  209. local isOpenGL = true
  210. local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
  211. x_scale = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
  212. x_offset = (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
  213. y_scale = 2 / (tanHalfFov.UpTan + tanHalfFov.DownTan )
  214. y_offset = (tanHalfFov.UpTan - tanHalfFov.DownTan ) * y_scale * 0.5
  215. local result = {
  216. Scale = vec2(x_scale, y_scale),
  217. Offset = vec2(x_offset, y_offset)
  218. }
  219. -- Hey - why is that Y.Offset negated?
  220. -- It's because a projection matrix transforms from world coords with Y=up,
  221. -- whereas this is from NDC which is Y=down.
  222. return result
  223. end
  224. if not flipZ and farAtInfinity then
  225. print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
  226. farAtInfinity = false
  227. end
  228. -- A projection matrix is very like a scaling from NDC, so we can start with that.
  229. local scaleAndOffset = CreateNDCScaleAndOffsetFromFov(tanHalfFov)
  230. local handednessScale = rightHanded and -1.0 or 1.0
  231. local projection = new()
  232. -- Produces X result, mapping clip edges to [-w,+w]
  233. projection[1] = scaleAndOffset.Scale.x
  234. projection[2] = 0
  235. projection[3] = handednessScale * scaleAndOffset.Offset.x
  236. projection[4] = 0
  237. -- Produces Y result, mapping clip edges to [-w,+w]
  238. -- Hey - why is that YOffset negated?
  239. -- It's because a projection matrix transforms from world coords with Y=up,
  240. -- whereas this is derived from an NDC scaling, which is Y=down.
  241. projection[5] = 0
  242. projection[6] = scaleAndOffset.Scale.y
  243. projection[7] = handednessScale * -scaleAndOffset.Offset.y
  244. projection[8] = 0
  245. -- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
  246. -- We'll just use some defaults for now.
  247. projection[9] = 0
  248. projection[10] = 0
  249. if farAtInfinity then
  250. if isOpenGL then
  251. -- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
  252. projection[11] = -handednessScale
  253. projection[12] = 2.0 * zNear
  254. else
  255. projection[11] = 0
  256. projection[12] = zNear
  257. end
  258. else
  259. if isOpenGL then
  260. -- Clip range is [-w,+w], so 0 is at the middle of the range.
  261. projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
  262. projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
  263. else
  264. -- Clip range is [0,+w], so 0 is at the start of the range.
  265. projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
  266. projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
  267. end
  268. end
  269. -- Produces W result (= Z in)
  270. projection[13] = 0
  271. projection[14] = 0
  272. projection[15] = handednessScale
  273. projection[16] = 0
  274. return projection:transpose(projection)
  275. end
  276. --- Clone a matrix.
  277. -- @tparam mat4 a Matrix to clone
  278. -- @treturn mat4 out
  279. function mat4.clone(a)
  280. return new(a)
  281. end
  282. --- Multiply two matrices.
  283. -- @tparam mat4 out Matrix to store the result
  284. -- @tparam mat4 a Left hand operand
  285. -- @tparam mat4 b Right hand operand
  286. -- @treturn mat4 out
  287. function mat4.mul(out, a, b)
  288. tm4[1] = a[1] * b[1] + a[2] * b[5] + a[3] * b[9] + a[4] * b[13]
  289. tm4[2] = a[1] * b[2] + a[2] * b[6] + a[3] * b[10] + a[4] * b[14]
  290. tm4[3] = a[1] * b[3] + a[2] * b[7] + a[3] * b[11] + a[4] * b[15]
  291. tm4[4] = a[1] * b[4] + a[2] * b[8] + a[3] * b[12] + a[4] * b[16]
  292. tm4[5] = a[5] * b[1] + a[6] * b[5] + a[7] * b[9] + a[8] * b[13]
  293. tm4[6] = a[5] * b[2] + a[6] * b[6] + a[7] * b[10] + a[8] * b[14]
  294. tm4[7] = a[5] * b[3] + a[6] * b[7] + a[7] * b[11] + a[8] * b[15]
  295. tm4[8] = a[5] * b[4] + a[6] * b[8] + a[7] * b[12] + a[8] * b[16]
  296. tm4[9] = a[9] * b[1] + a[10] * b[5] + a[11] * b[9] + a[12] * b[13]
  297. tm4[10] = a[9] * b[2] + a[10] * b[6] + a[11] * b[10] + a[12] * b[14]
  298. tm4[11] = a[9] * b[3] + a[10] * b[7] + a[11] * b[11] + a[12] * b[15]
  299. tm4[12] = a[9] * b[4] + a[10] * b[8] + a[11] * b[12] + a[12] * b[16]
  300. tm4[13] = a[13] * b[1] + a[14] * b[5] + a[15] * b[9] + a[16] * b[13]
  301. tm4[14] = a[13] * b[2] + a[14] * b[6] + a[15] * b[10] + a[16] * b[14]
  302. tm4[15] = a[13] * b[3] + a[14] * b[7] + a[15] * b[11] + a[16] * b[15]
  303. tm4[16] = a[13] * b[4] + a[14] * b[8] + a[15] * b[12] + a[16] * b[16]
  304. for i=1, 16 do
  305. out[i] = tm4[i]
  306. end
  307. return out
  308. end
  309. --- Multiply a matrix and a vec4.
  310. -- @tparam mat4 out Matrix to store the result
  311. -- @tparam mat4 a Left hand operand
  312. -- @tparam table b Right hand operand
  313. -- @treturn mat4 out
  314. function mat4.mul_vec4(out, a, b)
  315. tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9] + b[4] * a[13]
  316. tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
  317. tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
  318. tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
  319. for i=1, 4 do
  320. out[i] = tv4[i]
  321. end
  322. return out
  323. end
  324. --- Invert a matrix.
  325. -- @tparam mat4 out Matrix to store the result
  326. -- @tparam mat4 a Matrix to invert
  327. -- @treturn mat4 out
  328. function mat4.invert(out, a)
  329. 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]
  330. 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]
  331. 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]
  332. 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]
  333. 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]
  334. 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]
  335. 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]
  336. 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]
  337. 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]
  338. 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]
  339. 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]
  340. 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]
  341. 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]
  342. 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]
  343. 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]
  344. 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]
  345. for i=1, 16 do
  346. out[i] = tm4[i]
  347. end
  348. local det = a[1] * out[1] + a[2] * out[5] + a[3] * out[9] + a[4] * out[13]
  349. if det == 0 then return a end
  350. det = 1 / det
  351. for i = 1, 16 do
  352. out[i] = out[i] * det
  353. end
  354. return out
  355. end
  356. --- Scale a matrix.
  357. -- @tparam mat4 out Matrix to store the result
  358. -- @tparam mat4 a Matrix to scale
  359. -- @tparam vec3 s Scalar
  360. -- @treturn mat4 out
  361. function mat4.scale(out, a, s)
  362. identity(tmp)
  363. tmp[1] = s.x
  364. tmp[6] = s.y
  365. tmp[11] = s.z
  366. return out:mul(tmp, a)
  367. end
  368. --- Rotate a matrix.
  369. -- @tparam mat4 out Matrix to store the result
  370. -- @tparam mat4 a Matrix to rotate
  371. -- @tparam number angle Angle to rotate by (in radians)
  372. -- @tparam vec3 axis Axis to rotate on
  373. -- @treturn mat4 out
  374. function mat4.rotate(out, a, angle, axis)
  375. if type(angle) == "table" or type(angle) == "cdata" then
  376. angle, axis = angle:to_angle_axis()
  377. end
  378. local l = axis:len()
  379. if l == 0 then
  380. return a
  381. end
  382. local x, y, z = axis.x / l, axis.y / l, axis.z / l
  383. local c = cos(angle)
  384. local s = sin(angle)
  385. identity(tmp)
  386. tmp[1] = x * x * (1 - c) + c
  387. tmp[2] = y * x * (1 - c) + z * s
  388. tmp[3] = x * z * (1 - c) - y * s
  389. tmp[5] = x * y * (1 - c) - z * s
  390. tmp[6] = y * y * (1 - c) + c
  391. tmp[7] = y * z * (1 - c) + x * s
  392. tmp[9] = x * z * (1 - c) + y * s
  393. tmp[10] = y * z * (1 - c) - x * s
  394. tmp[11] = z * z * (1 - c) + c
  395. return out:mul(tmp, a)
  396. end
  397. --- Translate a matrix.
  398. -- @tparam mat4 out Matrix to store the result
  399. -- @tparam mat4 a Matrix to translate
  400. -- @tparam vec3 t Translation vector
  401. -- @treturn mat4 out
  402. function mat4.translate(out, a, t)
  403. identity(tmp)
  404. tmp[13] = t.x
  405. tmp[14] = t.y
  406. tmp[15] = t.z
  407. return out:mul(tmp, a)
  408. end
  409. --- Shear a matrix.
  410. -- @tparam mat4 out Matrix to store the result
  411. -- @tparam mat4 a Matrix to translate
  412. -- @tparam number yx
  413. -- @tparam number zx
  414. -- @tparam number xy
  415. -- @tparam number zy
  416. -- @tparam number xz
  417. -- @tparam number yz
  418. -- @treturn mat4 out
  419. function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
  420. identity(tmp)
  421. tmp[2] = yx or 0
  422. tmp[3] = zx or 0
  423. tmp[5] = xy or 0
  424. tmp[7] = zy or 0
  425. tmp[9] = xz or 0
  426. tmp[10] = yz or 0
  427. return out:mul(tmp, a)
  428. end
  429. --- Reflect a matrix across a plane.
  430. -- @tparam mat4 Matrix to store the result
  431. -- @tparam a Matrix to reflect
  432. -- @tparam vec3 position A point on the plane
  433. -- @tparam vec3 normal The (normalized!) normal vector of the plane
  434. function mat4.reflect(out, a, position, normal)
  435. local nx, ny, nz = normal:unpack()
  436. local d = -position:dot(normal)
  437. tmp[1] = 1 - 2 * nx ^ 2
  438. tmp[2] = 2 * nx * ny
  439. tmp[3] = -2 * nx * nz
  440. tmp[4] = 0
  441. tmp[5] = -2 * nx * ny
  442. tmp[6] = 1 - 2 * ny ^ 2
  443. tmp[7] = -2 * ny * nz
  444. tmp[8] = 0
  445. tmp[9] = -2 * nx * nz
  446. tmp[10] = -2 * ny * nz
  447. tmp[11] = 1 - 2 * nz ^ 2
  448. tmp[12] = 0
  449. tmp[13] = -2 * nx * d
  450. tmp[14] = -2 * ny * d
  451. tmp[15] = -2 * nz * d
  452. tmp[16] = 1
  453. return out:mul(tmp, a)
  454. end
  455. --- Transform matrix to look at a point.
  456. -- @tparam mat4 out Matrix to store result
  457. -- @tparam mat4 a Matrix to transform
  458. -- @tparam vec3 eye Location of viewer's view plane
  459. -- @tparam vec3 center Location of object to view
  460. -- @tparam vec3 up Up direction
  461. -- @treturn mat4 out
  462. function mat4.look_at(out, a, eye, look_at, up)
  463. local z_axis = (eye - look_at):normalize()
  464. local x_axis = up:cross(z_axis):normalize()
  465. local y_axis = z_axis:cross(x_axis)
  466. out[1] = x_axis.x
  467. out[2] = y_axis.x
  468. out[3] = z_axis.x
  469. out[4] = 0
  470. out[5] = x_axis.y
  471. out[6] = y_axis.y
  472. out[7] = z_axis.y
  473. out[8] = 0
  474. out[9] = x_axis.z
  475. out[10] = y_axis.z
  476. out[11] = z_axis.z
  477. out[12] = 0
  478. out[13] = 0
  479. out[14] = 0
  480. out[15] = 0
  481. out[16] = 1
  482. return out
  483. end
  484. --- Transpose a matrix.
  485. -- @tparam mat4 out Matrix to store the result
  486. -- @tparam mat4 a Matrix to transpose
  487. -- @treturn mat4 out
  488. function mat4.transpose(out, a)
  489. tm4[1] = a[1]
  490. tm4[2] = a[5]
  491. tm4[3] = a[9]
  492. tm4[4] = a[13]
  493. tm4[5] = a[2]
  494. tm4[6] = a[6]
  495. tm4[7] = a[10]
  496. tm4[8] = a[14]
  497. tm4[9] = a[3]
  498. tm4[10] = a[7]
  499. tm4[11] = a[11]
  500. tm4[12] = a[15]
  501. tm4[13] = a[4]
  502. tm4[14] = a[8]
  503. tm4[15] = a[12]
  504. tm4[16] = a[16]
  505. for i=1, 16 do
  506. out[i] = tm4[i]
  507. end
  508. return out
  509. end
  510. -- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L518
  511. --- Project a matrix from world space to screen space.
  512. -- @tparam vec3 obj Object position in world space
  513. -- @tparam mat4 view View matrix
  514. -- @tparam mat4 projection Projection matrix
  515. -- @tparam table viewport XYWH of viewport
  516. -- @treturn vec3 win
  517. function mat4.project(obj, view, projection, viewport)
  518. local position = { obj.x, obj.y, obj.z, 1 }
  519. mat4.mul_vec4(position, view, position)
  520. mat4.mul_vec4(position, projection, position)
  521. position[1] = position[1] / position[4] * 0.5 + 0.5
  522. position[2] = position[2] / position[4] * 0.5 + 0.5
  523. position[3] = position[3] / position[4] * 0.5 + 0.5
  524. position[1] = position[1] * viewport[3] + viewport[1]
  525. position[2] = position[2] * viewport[4] + viewport[2]
  526. return vec3(position[1], position[2], position[3])
  527. end
  528. -- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L544
  529. --- Unproject a matrix from screen space to world space.
  530. -- @tparam vec3 win Object position in screen space
  531. -- @tparam mat4 view View matrix
  532. -- @tparam mat4 projection Projection matrix
  533. -- @tparam table viewport XYWH of viewport
  534. -- @treturn vec3 obj
  535. function mat4.unproject(win, view, projection, viewport)
  536. local position = { win.x, win.y, win.z, 1 }
  537. position[1] = (position[1] - viewport[1]) / viewport[3]
  538. position[2] = (position[2] - viewport[2]) / viewport[4]
  539. position[1] = position[1] * 2 - 1
  540. position[2] = position[2] * 2 - 1
  541. position[3] = position[3] * 2 - 1
  542. tmp:mul(projection, view):invert(tmp)
  543. mat4.mul_vec4(position, tmp, position)
  544. position[1] = position[1] / position[4]
  545. position[2] = position[2] / position[4]
  546. position[3] = position[3] / position[4]
  547. return vec3(position[1], position[2], position[3])
  548. end
  549. --- Return a boolean showing if a table is or is not a mat4.
  550. -- @tparam mat4 a Matrix to be tested
  551. -- @treturn boolean is_mat4
  552. function mat4.is_mat4(a)
  553. if type(a) == "cdata" then
  554. return ffi.istype("cpml_mat4", a)
  555. end
  556. if type(a) ~= "table" then
  557. return false
  558. end
  559. for i = 1, 16 do
  560. if type(a[i]) ~= "number" then
  561. return false
  562. end
  563. end
  564. return true
  565. end
  566. --- Return a formatted string.
  567. -- @tparam mat4 a Matrix to be turned into a string
  568. -- @treturn string formatted
  569. function mat4.to_string(a)
  570. local str = "[ "
  571. for i = 1, 16 do
  572. str = str .. string.format("%+0.3f", a[i])
  573. if i < 16 then
  574. str = str .. ", "
  575. end
  576. end
  577. str = str .. " ]"
  578. return str
  579. end
  580. --- Convert a matrix to vec4s.
  581. -- @tparam mat4 a Matrix to be converted
  582. -- @treturn table vec4s
  583. function mat4.to_vec4s(a)
  584. return {
  585. { a[1], a[2], a[3], a[4] },
  586. { a[5], a[6], a[7], a[8] },
  587. { a[9], a[10], a[11], a[12] },
  588. { a[13], a[14], a[15], a[16] }
  589. }
  590. end
  591. -- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
  592. --- Convert a matrix to a quaternion.
  593. -- @tparam mat4 a Matrix to be converted
  594. -- @treturn quat out
  595. function mat4.to_quat(a)
  596. identity(tmp):transpose(a)
  597. local w = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
  598. local scale = w * 4
  599. local q = quat.new(
  600. tmp[10] - tmp[7] / scale,
  601. tmp[3] - tmp[9] / scale,
  602. tmp[5] - tmp[2] / scale,
  603. w
  604. )
  605. return q:normalize(q)
  606. end
  607. -- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
  608. --- Convert a matrix to a frustum.
  609. -- @tparam mat4 a Matrix to be converted (projection * view)
  610. -- @tparam boolean infinite Infinite removes the far plane
  611. -- @treturn frustum out
  612. function mat4.to_frustum(a, infinite)
  613. local t
  614. local frustum = {}
  615. -- Extract the LEFT plane
  616. frustum.left = {}
  617. frustum.left.a = a[4] + a[1]
  618. frustum.left.b = a[8] + a[5]
  619. frustum.left.c = a[12] + a[9]
  620. frustum.left.d = a[16] + a[13]
  621. -- Normalize the result
  622. t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
  623. frustum.left.a = frustum.left.a / t
  624. frustum.left.b = frustum.left.b / t
  625. frustum.left.c = frustum.left.c / t
  626. frustum.left.d = frustum.left.d / t
  627. -- Extract the RIGHT plane
  628. frustum.right = {}
  629. frustum.right.a = a[4] - a[1]
  630. frustum.right.b = a[8] - a[5]
  631. frustum.right.c = a[12] - a[9]
  632. frustum.right.d = a[16] - a[13]
  633. -- Normalize the result
  634. t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
  635. frustum.right.a = frustum.right.a / t
  636. frustum.right.b = frustum.right.b / t
  637. frustum.right.c = frustum.right.c / t
  638. frustum.right.d = frustum.right.d / t
  639. -- Extract the BOTTOM plane
  640. frustum.bottom = {}
  641. frustum.bottom.a = a[4] + a[2]
  642. frustum.bottom.b = a[8] + a[6]
  643. frustum.bottom.c = a[12] + a[10]
  644. frustum.bottom.d = a[16] + a[14]
  645. -- Normalize the result
  646. t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
  647. frustum.bottom.a = frustum.bottom.a / t
  648. frustum.bottom.b = frustum.bottom.b / t
  649. frustum.bottom.c = frustum.bottom.c / t
  650. frustum.bottom.d = frustum.bottom.d / t
  651. -- Extract the TOP plane
  652. frustum.top = {}
  653. frustum.top.a = a[4] - a[2]
  654. frustum.top.b = a[8] - a[6]
  655. frustum.top.c = a[12] - a[10]
  656. frustum.top.d = a[16] - a[14]
  657. -- Normalize the result
  658. t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
  659. frustum.top.a = frustum.top.a / t
  660. frustum.top.b = frustum.top.b / t
  661. frustum.top.c = frustum.top.c / t
  662. frustum.top.d = frustum.top.d / t
  663. -- Extract the NEAR plane
  664. frustum.near = {}
  665. frustum.near.a = a[4] + a[3]
  666. frustum.near.b = a[8] + a[7]
  667. frustum.near.c = a[12] + a[11]
  668. frustum.near.d = a[16] + a[15]
  669. -- Normalize the result
  670. t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
  671. frustum.near.a = frustum.near.a / t
  672. frustum.near.b = frustum.near.b / t
  673. frustum.near.c = frustum.near.c / t
  674. frustum.near.d = frustum.near.d / t
  675. if not infinite then
  676. -- Extract the FAR plane
  677. frustum.far = {}
  678. frustum.far.a = a[4] - a[3]
  679. frustum.far.b = a[8] - a[7]
  680. frustum.far.c = a[12] - a[11]
  681. frustum.far.d = a[16] - a[15]
  682. -- Normalize the result
  683. t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
  684. frustum.far.a = frustum.far.a / t
  685. frustum.far.b = frustum.far.b / t
  686. frustum.far.c = frustum.far.c / t
  687. frustum.far.d = frustum.far.d / t
  688. end
  689. return frustum
  690. end
  691. function mat4_mt.__index(t, k)
  692. if type(t) == "cdata" then
  693. if type(k) == "number" then
  694. return t._m[k-1]
  695. end
  696. end
  697. return rawget(mat4, k)
  698. end
  699. function mat4_mt.__newindex(t, k, v)
  700. if type(t) == "cdata" then
  701. if type(k) == "number" then
  702. t._m[k-1] = v
  703. end
  704. end
  705. end
  706. mat4_mt.__tostring = mat4.to_string
  707. function mat4_mt.__call(_, a)
  708. return mat4.new(a)
  709. end
  710. function mat4_mt.__unm(a)
  711. return new():invert(a)
  712. end
  713. function mat4_mt.__eq(a, b)
  714. if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
  715. return false
  716. end
  717. for i = 1, 16 do
  718. if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
  719. return false
  720. end
  721. end
  722. return true
  723. end
  724. function mat4_mt.__mul(a, b)
  725. assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operand. (<cpml.mat4> expected)")
  726. if vec3.is_vec3(b) then
  727. return vec3(mat4.mul_vec4({}, a, { b.x, b.y, b.z, 1 }))
  728. end
  729. assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
  730. if mat4.is_mat4(b) then
  731. return new():mul(a, b)
  732. end
  733. return mat4.mul_vec4({}, a, b)
  734. end
  735. if status then
  736. ffi.metatype(new, mat4_mt)
  737. end
  738. return setmetatable({}, mat4_mt)