--- double 4x4, 1-based, column major matrices -- @module mat4 local modules = (...):gsub('%.[^%.]+$', '') .. "." local constants = require(modules .. "constants") local vec2 = require(modules .. "vec2") local vec3 = require(modules .. "vec3") local quat = require(modules .. "quat") local utils = require(modules .. "utils") local precond = require(modules .. "_private_precond") local private = require(modules .. "_private_utils") local sqrt = math.sqrt local cos = math.cos local sin = math.sin local tan = math.tan local rad = math.rad local mat4 = {} local mat4_mt = {} -- Private constructor. local function new(m) m = m or { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } m._m = m return setmetatable(m, mat4_mt) end -- Convert matrix into identity local function identity(m) m[1], m[2], m[3], m[4] = 1, 0, 0, 0 m[5], m[6], m[7], m[8] = 0, 1, 0, 0 m[9], m[10], m[11], m[12] = 0, 0, 1, 0 m[13], m[14], m[15], m[16] = 0, 0, 0, 1 return m end -- Do the check to see if JIT is enabled. If so use the optimized FFI structs. local status, ffi if type(jit) == "table" and jit.status() then -- status, ffi = pcall(require, "ffi") if status then ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;" new = ffi.typeof("cpml_mat4") end end -- Statically allocate a temporary variable used in some of our functions. local tmp = new() local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } local tv4 = { 0, 0, 0, 0 } --- The public constructor. -- @param a Can be of four types:
-- table Length 16 (4x4 matrix) -- table Length 9 (3x3 matrix) -- table Length 4 (4 vec4s) -- nil -- @treturn mat4 out function mat4.new(a) local out = new() -- 4x4 matrix if type(a) == "table" and #a == 16 then for i = 1, 16 do out[i] = tonumber(a[i]) end -- 3x3 matrix elseif type(a) == "table" and #a == 9 then out[1], out[2], out[3] = a[1], a[2], a[3] out[5], out[6], out[7] = a[4], a[5], a[6] out[9], out[10], out[11] = a[7], a[8], a[9] out[16] = 1 -- 4 vec4s elseif type(a) == "table" and type(a[1]) == "table" then local idx = 1 for i = 1, 4 do for j = 1, 4 do out[idx] = a[i][j] idx = idx + 1 end end -- nil else out[1] = 1 out[6] = 1 out[11] = 1 out[16] = 1 end return out end --- Create an identity matrix. -- @tparam mat4 a Matrix to overwrite -- @treturn mat4 out function mat4.identity(a) return identity(a or new()) end --- Create a matrix from an angle/axis pair. -- @tparam number angle Angle of rotation -- @tparam vec3 axis Axis of rotation -- @treturn mat4 out function mat4.from_angle_axis(angle, axis) local l = axis:len() if l == 0 then return new() end local x, y, z = axis.x / l, axis.y / l, axis.z / l local c = cos(angle) local s = sin(angle) return new { x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0, x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0, x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0, 0, 0, 0, 1 } end --- Create a matrix from a quaternion. -- @tparam quat q Rotation quaternion -- @treturn mat4 out function mat4.from_quaternion(q) return mat4.from_angle_axis(q:to_angle_axis()) end --- Create a matrix from a direction/up pair. -- @tparam vec3 direction Vector direction -- @tparam vec3 up Up direction -- @treturn mat4 out function mat4.from_direction(direction, up) local forward = vec3.normalize(direction) local side = vec3.cross(forward, up):normalize() local new_up = vec3.cross(side, forward):normalize() local out = new() out[1] = side.x out[5] = side.y out[9] = side.z out[2] = new_up.x out[6] = new_up.y out[10] = new_up.z out[3] = forward.x out[7] = forward.y out[11] = forward.z out[16] = 1 return out end --- Create a matrix from a transform. -- @tparam vec3 trans Translation vector -- @tparam quat rot Rotation quaternion -- @tparam vec3 scale Scale vector -- @treturn mat4 out function mat4.from_transform(trans, rot, scale) local rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w local sm = new { scale.x, 0, 0, 0, 0, scale.y, 0, 0, 0, 0, scale.z, 0, 0, 0, 0, 1, } local rm = new { 1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0, 2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0, 2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0, 0, 0, 0, 1 } local rsm = rm * sm rsm[13] = trans.x rsm[14] = trans.y rsm[15] = trans.z return rsm end --- Create matrix from orthogonal. -- @tparam number left -- @tparam number right -- @tparam number top -- @tparam number bottom -- @tparam number near -- @tparam number far -- @treturn mat4 out function mat4.from_ortho(left, right, top, bottom, near, far) local out = new() out[1] = 2 / (right - left) out[6] = 2 / (top - bottom) out[11] = -2 / (far - near) out[13] = -((right + left) / (right - left)) out[14] = -((top + bottom) / (top - bottom)) out[15] = -((far + near) / (far - near)) out[16] = 1 return out end --- Create matrix from perspective. -- @tparam number fovy Field of view -- @tparam number aspect Aspect ratio -- @tparam number near Near plane -- @tparam number far Far plane -- @treturn mat4 out function mat4.from_perspective(fovy, aspect, near, far) assert(aspect ~= 0) assert(near ~= far) local t = tan(rad(fovy) / 2) local out = new() out[1] = 1 / (t * aspect) out[6] = 1 / t out[11] = -(far + near) / (far - near) out[12] = -1 out[15] = -(2 * far * near) / (far - near) out[16] = 0 return out end -- Adapted from the Oculus SDK. --- Create matrix from HMD perspective. -- @tparam number tanHalfFov Tangent of half of the field of view -- @tparam number zNear Near plane -- @tparam number zFar Far plane -- @tparam boolean flipZ Z axis is flipped or not -- @tparam boolean farAtInfinity Far plane is infinite or not -- @treturn mat4 out function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity) -- CPML is right-handed and intended for GL, so these don't need to be arguments. local rightHanded = true local isOpenGL = true local function CreateNDCScaleAndOffsetFromFov(tanHalfFov) local x_scale = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan) local x_offset = (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5 local y_scale = 2 / (tanHalfFov.UpTan + tanHalfFov.DownTan ) local y_offset = (tanHalfFov.UpTan - tanHalfFov.DownTan ) * y_scale * 0.5 local result = { Scale = vec2(x_scale, y_scale), Offset = vec2(x_offset, y_offset) } -- Hey - why is that Y.Offset negated? -- It's because a projection matrix transforms from world coords with Y=up, -- whereas this is from NDC which is Y=down. return result end if not flipZ and farAtInfinity then print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped") farAtInfinity = false end -- A projection matrix is very like a scaling from NDC, so we can start with that. local scaleAndOffset = CreateNDCScaleAndOffsetFromFov(tanHalfFov) local handednessScale = rightHanded and -1.0 or 1.0 local projection = new() -- Produces X result, mapping clip edges to [-w,+w] projection[1] = scaleAndOffset.Scale.x projection[2] = 0 projection[3] = handednessScale * scaleAndOffset.Offset.x projection[4] = 0 -- Produces Y result, mapping clip edges to [-w,+w] -- Hey - why is that YOffset negated? -- It's because a projection matrix transforms from world coords with Y=up, -- whereas this is derived from an NDC scaling, which is Y=down. projection[5] = 0 projection[6] = scaleAndOffset.Scale.y projection[7] = handednessScale * -scaleAndOffset.Offset.y projection[8] = 0 -- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants. -- We'll just use some defaults for now. projection[9] = 0 projection[10] = 0 if farAtInfinity then if isOpenGL then -- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D. projection[11] = -handednessScale projection[12] = 2.0 * zNear else projection[11] = 0 projection[12] = zNear end else if isOpenGL then -- Clip range is [-w,+w], so 0 is at the middle of the range. projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar) projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar) else -- Clip range is [0,+w], so 0 is at the start of the range. projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar) projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar) end end -- Produces W result (= Z in) projection[13] = 0 projection[14] = 0 projection[15] = handednessScale projection[16] = 0 return projection:transpose(projection) end --- Clone a matrix. -- @tparam mat4 a Matrix to clone -- @treturn mat4 out function mat4.clone(a) return new(a) end function mul_internal(out, a, b) tm4[1] = b[1] * a[1] + b[2] * a[5] + b[3] * a[9] + b[4] * a[13] tm4[2] = b[1] * a[2] + b[2] * a[6] + b[3] * a[10] + b[4] * a[14] tm4[3] = b[1] * a[3] + b[2] * a[7] + b[3] * a[11] + b[4] * a[15] tm4[4] = b[1] * a[4] + b[2] * a[8] + b[3] * a[12] + b[4] * a[16] tm4[5] = b[5] * a[1] + b[6] * a[5] + b[7] * a[9] + b[8] * a[13] tm4[6] = b[5] * a[2] + b[6] * a[6] + b[7] * a[10] + b[8] * a[14] tm4[7] = b[5] * a[3] + b[6] * a[7] + b[7] * a[11] + b[8] * a[15] tm4[8] = b[5] * a[4] + b[6] * a[8] + b[7] * a[12] + b[8] * a[16] tm4[9] = b[9] * a[1] + b[10] * a[5] + b[11] * a[9] + b[12] * a[13] tm4[10] = b[9] * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14] tm4[11] = b[9] * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15] tm4[12] = b[9] * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16] tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9] + b[16] * a[13] tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14] tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15] tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16] for i = 1, 16 do out[i] = tm4[i] end end --- Multiply N matrices. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 or {mat4, ...} left hand operand(s) -- @tparam mat4 right hand operand if a is not table -- @treturn mat4 out multiplied matrix result function mat4.mul(out, a, b) if mat4.is_mat4(a) then mul_internal(out, a, b) return out end if #a == 0 then identity(out) elseif #a == 1 then -- only one matrix, just copy for i = 1, 16 do out[i] = a[1][i] end else local ma = a[1] local mb = a[2] for i = 2, #a do mul_internal(out, ma, mb) ma = out end end return out end --- Multiply a matrix and a vec3, with perspective division. -- This function uses an implicit 1 for the fourth component. -- @tparam vec3 out vec3 to store the result -- @tparam mat4 a Left hand operand -- @tparam vec3 b Right hand operand -- @treturn vec3 out function mat4.mul_vec3_perspective(out, a, b) local v4x = b.x * a[1] + b.y * a[5] + b.z * a[9] + a[13] local v4y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14] local v4z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15] local v4w = b.x * a[4] + b.y * a[8] + b.z * a[12] + a[16] local inv_w = 0 if v4w ~= 0 then inv_w = utils.sign(v4w) / v4w end out.x = v4x * inv_w out.y = v4y * inv_w out.z = v4z * inv_w return out end --- Multiply a matrix and a vec4. -- @tparam table out table to store the result -- @tparam mat4 a Left hand operand -- @tparam table b Right hand operand -- @treturn vec4 out function mat4.mul_vec4(out, a, b) tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9] + b[4] * a[13] tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14] tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15] tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16] for i = 1, 4 do out[i] = tv4[i] end return out end --- Invert a matrix. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 a Matrix to invert -- @treturn mat4 out function mat4.invert(out, a) 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] local det = a[1] * tm4[1] + a[2] * tm4[5] + a[3] * tm4[9] + a[4] * tm4[13] if det == 0 then return a end det = 1 / det for i = 1, 16 do out[i] = tm4[i] * det end return out end --- Scale a matrix. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 a Matrix to scale -- @tparam vec3 s Scalar -- @treturn mat4 out function mat4.scale(out, a, s) identity(tmp) tmp[1] = s.x tmp[6] = s.y tmp[11] = s.z return out:mul(tmp, a) end --- Rotate a matrix. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 a Matrix to rotate -- @tparam number angle Angle to rotate by (in radians) -- @tparam vec3 axis Axis to rotate on -- @treturn mat4 out function mat4.rotate(out, a, angle, axis) if type(angle) == "table" or type(angle) == "cdata" then angle, axis = angle:to_angle_axis() end local l = axis:len() if l == 0 then return a end local x, y, z = axis.x / l, axis.y / l, axis.z / l local c = cos(angle) local s = sin(angle) identity(tmp) tmp[1] = x * x * (1 - c) + c tmp[2] = y * x * (1 - c) + z * s tmp[3] = x * z * (1 - c) - y * s tmp[5] = x * y * (1 - c) - z * s tmp[6] = y * y * (1 - c) + c tmp[7] = y * z * (1 - c) + x * s tmp[9] = x * z * (1 - c) + y * s tmp[10] = y * z * (1 - c) - x * s tmp[11] = z * z * (1 - c) + c return out:mul(tmp, a) end --- Translate a matrix. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 a Matrix to translate -- @tparam vec3 t Translation vector -- @treturn mat4 out function mat4.translate(out, a, t) identity(tmp) tmp[13] = t.x tmp[14] = t.y tmp[15] = t.z return out:mul(tmp, a) end --- Shear a matrix. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 a Matrix to translate -- @tparam number yx -- @tparam number zx -- @tparam number xy -- @tparam number zy -- @tparam number xz -- @tparam number yz -- @treturn mat4 out function mat4.shear(out, a, yx, zx, xy, zy, xz, yz) identity(tmp) tmp[2] = yx or 0 tmp[3] = zx or 0 tmp[5] = xy or 0 tmp[7] = zy or 0 tmp[9] = xz or 0 tmp[10] = yz or 0 return out:mul(tmp, a) end --- Reflect a matrix across a plane. -- @tparam mat4 Matrix to store the result -- @tparam a Matrix to reflect -- @tparam vec3 position A point on the plane -- @tparam vec3 normal The (normalized!) normal vector of the plane function mat4.reflect(out, a, position, normal) local nx, ny, nz = normal:unpack() local d = -position:dot(normal) tmp[1] = 1 - 2 * nx ^ 2 tmp[2] = 2 * nx * ny tmp[3] = -2 * nx * nz tmp[4] = 0 tmp[5] = -2 * nx * ny tmp[6] = 1 - 2 * ny ^ 2 tmp[7] = -2 * ny * nz tmp[8] = 0 tmp[9] = -2 * nx * nz tmp[10] = -2 * ny * nz tmp[11] = 1 - 2 * nz ^ 2 tmp[12] = 0 tmp[13] = -2 * nx * d tmp[14] = -2 * ny * d tmp[15] = -2 * nz * d tmp[16] = 1 return out:mul(tmp, a) end --- Transform matrix to look at a point. -- @tparam mat4 out Matrix to store result -- @tparam vec3 eye Location of viewer's view plane -- @tparam vec3 center Location of object to view -- @tparam vec3 up Up direction -- @treturn mat4 out function mat4.look_at(out, eye, look_at, up) local z_axis = (eye - look_at):normalize() local x_axis = up:cross(z_axis):normalize() local y_axis = z_axis:cross(x_axis) out[1] = x_axis.x out[2] = y_axis.x out[3] = z_axis.x out[4] = 0 out[5] = x_axis.y out[6] = y_axis.y out[7] = z_axis.y out[8] = 0 out[9] = x_axis.z out[10] = y_axis.z out[11] = z_axis.z out[12] = 0 out[13] = -out[ 1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z out[14] = -out[ 2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z out[15] = -out[ 3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z out[16] = -out[ 4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1 return out end --- Transform matrix to target a point. -- @tparam mat4 out Matrix to store result -- @tparam vec3 eye Location of viewer's view plane -- @tparam vec3 center Location of object to view -- @tparam vec3 up Up direction -- @treturn mat4 out function mat4.target(out, from, to, up) local z_axis = (from - to):normalize() local x_axis = up:cross(z_axis):normalize() local y_axis = z_axis:cross(x_axis) out[1] = x_axis.x out[2] = x_axis.y out[3] = x_axis.z out[4] = 0 out[5] = y_axis.x out[6] = y_axis.y out[7] = y_axis.z out[8] = 0 out[9] = z_axis.x out[10] = z_axis.y out[11] = z_axis.z out[12] = 0 out[13] = from.x out[14] = from.y out[15] = from.z out[16] = 1 return out end --- Transpose a matrix. -- @tparam mat4 out Matrix to store the result -- @tparam mat4 a Matrix to transpose -- @treturn mat4 out function mat4.transpose(out, a) tm4[1] = a[1] tm4[2] = a[5] tm4[3] = a[9] tm4[4] = a[13] tm4[5] = a[2] tm4[6] = a[6] tm4[7] = a[10] tm4[8] = a[14] tm4[9] = a[3] tm4[10] = a[7] tm4[11] = a[11] tm4[12] = a[15] tm4[13] = a[4] tm4[14] = a[8] tm4[15] = a[12] tm4[16] = a[16] for i = 1, 16 do out[i] = tm4[i] end return out end --- Project a point into screen space -- @tparam vec3 obj Object position in world space -- @tparam mat4 mvp Projection matrix -- @tparam table viewport XYWH of viewport -- @treturn vec3 win function mat4.project(obj, mvp, viewport) local point = mat4.mul_vec3_perspective(vec3(), mvp, obj) point.x = point.x * 0.5 + 0.5 point.y = point.y * 0.5 + 0.5 point.z = point.z * 0.5 + 0.5 point.x = point.x * viewport[3] + viewport[1] point.y = point.y * viewport[4] + viewport[2] return point end --- Unproject a point from screen space to world space. -- @tparam vec3 win Object position in screen space -- @tparam mat4 mvp Projection matrix -- @tparam table viewport XYWH of viewport -- @treturn vec3 obj function mat4.unproject(win, mvp, viewport) local point = vec3.clone(win) -- 0..n -> 0..1 point.x = (point.x - viewport[1]) / viewport[3] point.y = (point.y - viewport[2]) / viewport[4] -- 0..1 -> -1..1 point.x = point.x * 2 - 1 point.y = point.y * 2 - 1 point.z = point.z * 2 - 1 return mat4.mul_vec3_perspective(point, tmp:invert(mvp), point) end --- Return a boolean showing if a table is or is not a mat4. -- @tparam mat4 a Matrix to be tested -- @treturn boolean is_mat4 function mat4.is_mat4(a) if type(a) == "cdata" then return ffi.istype("cpml_mat4", a) end if type(a) ~= "table" then return false end for i = 1, 16 do if type(a[i]) ~= "number" then return false end end return true end --- Return whether any component is NaN -- @tparam mat4 a Matrix to be tested -- @treturn boolean if any component is NaN function vec2.has_nan(a) for i = 1, 16 do if private.is_nan(a[i]) then return true end end return false end --- Return a formatted string. -- @tparam mat4 a Matrix to be turned into a string -- @treturn string formatted function mat4.to_string(a) local str = "[ " for i = 1, 16 do str = str .. string.format("%+0.3f", a[i]) if i < 16 then str = str .. ", " end end str = str .. " ]" return str end --- Convert a matrix to row vec4s. -- @tparam mat4 a Matrix to be converted -- @treturn table vec4s function mat4.to_vec4s(a) return { { a[1], a[2], a[3], a[4] }, { a[5], a[6], a[7], a[8] }, { a[9], a[10], a[11], a[12] }, { a[13], a[14], a[15], a[16] } } end --- Convert a matrix to col vec4s. -- @tparam mat4 a Matrix to be converted -- @treturn table vec4s function mat4.to_vec4s_cols(a) return { { a[1], a[5], a[9], a[13] }, { a[2], a[6], a[10], a[14] }, { a[3], a[7], a[11], a[15] }, { a[4], a[8], a[12], a[16] } } end -- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ --- Convert a matrix to a quaternion. -- @tparam mat4 a Matrix to be converted -- @treturn quat out function mat4.to_quat(a) identity(tmp):transpose(a) local w = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2 local scale = w * 4 local q = quat.new( tmp[10] - tmp[7] / scale, tmp[3] - tmp[9] / scale, tmp[5] - tmp[2] / scale, w ) return q:normalize(q) end -- http://www.crownandcutlass.com/features/technicaldetails/frustum.html --- Convert a matrix to a frustum. -- @tparam mat4 a Matrix to be converted (projection * view) -- @tparam boolean infinite Infinite removes the far plane -- @treturn frustum out function mat4.to_frustum(a, infinite) local t local frustum = {} -- Extract the LEFT plane frustum.left = {} frustum.left.a = a[4] + a[1] frustum.left.b = a[8] + a[5] frustum.left.c = a[12] + a[9] frustum.left.d = a[16] + a[13] -- Normalize the result t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c) frustum.left.a = frustum.left.a / t frustum.left.b = frustum.left.b / t frustum.left.c = frustum.left.c / t frustum.left.d = frustum.left.d / t -- Extract the RIGHT plane frustum.right = {} frustum.right.a = a[4] - a[1] frustum.right.b = a[8] - a[5] frustum.right.c = a[12] - a[9] frustum.right.d = a[16] - a[13] -- Normalize the result t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c) frustum.right.a = frustum.right.a / t frustum.right.b = frustum.right.b / t frustum.right.c = frustum.right.c / t frustum.right.d = frustum.right.d / t -- Extract the BOTTOM plane frustum.bottom = {} frustum.bottom.a = a[4] + a[2] frustum.bottom.b = a[8] + a[6] frustum.bottom.c = a[12] + a[10] frustum.bottom.d = a[16] + a[14] -- Normalize the result t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c) frustum.bottom.a = frustum.bottom.a / t frustum.bottom.b = frustum.bottom.b / t frustum.bottom.c = frustum.bottom.c / t frustum.bottom.d = frustum.bottom.d / t -- Extract the TOP plane frustum.top = {} frustum.top.a = a[4] - a[2] frustum.top.b = a[8] - a[6] frustum.top.c = a[12] - a[10] frustum.top.d = a[16] - a[14] -- Normalize the result t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c) frustum.top.a = frustum.top.a / t frustum.top.b = frustum.top.b / t frustum.top.c = frustum.top.c / t frustum.top.d = frustum.top.d / t -- Extract the NEAR plane frustum.near = {} frustum.near.a = a[4] + a[3] frustum.near.b = a[8] + a[7] frustum.near.c = a[12] + a[11] frustum.near.d = a[16] + a[15] -- Normalize the result t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c) frustum.near.a = frustum.near.a / t frustum.near.b = frustum.near.b / t frustum.near.c = frustum.near.c / t frustum.near.d = frustum.near.d / t if not infinite then -- Extract the FAR plane frustum.far = {} frustum.far.a = a[4] - a[3] frustum.far.b = a[8] - a[7] frustum.far.c = a[12] - a[11] frustum.far.d = a[16] - a[15] -- Normalize the result t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c) frustum.far.a = frustum.far.a / t frustum.far.b = frustum.far.b / t frustum.far.c = frustum.far.c / t frustum.far.d = frustum.far.d / t end return frustum end function mat4_mt.__index(t, k) if type(t) == "cdata" then if type(k) == "number" then return t._m[k-1] end end return rawget(mat4, k) end function mat4_mt.__newindex(t, k, v) if type(t) == "cdata" then if type(k) == "number" then t._m[k-1] = v end end end mat4_mt.__tostring = mat4.to_string function mat4_mt.__call(_, a) return mat4.new(a) end function mat4_mt.__unm(a) return new():invert(a) end function mat4_mt.__eq(a, b) if not mat4.is_mat4(a) or not mat4.is_mat4(b) then return false end for i = 1, 16 do if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then return false end end return true end function mat4_mt.__mul(a, b) precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. ( expected)", type(a)) if vec3.is_vec3(b) then return mat4.mul_vec3_perspective(vec3(), a, b) end assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. ( or table #4 expected)") if mat4.is_mat4(b) then return new():mul(a, b) end return mat4.mul_vec4({}, a, b) end if status then xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded ffi.metatype(new, mat4_mt) end, function() end) end return setmetatable({}, mat4_mt)