mat4.lua 22 KB

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