diff --git a/unit_tests/irrlicht_luanti_tests.lua b/unit_tests/irrlicht_luanti_tests.lua new file mode 100644 index 0000000..da24315 --- /dev/null +++ b/unit_tests/irrlicht_luanti_tests.lua @@ -0,0 +1,147 @@ +local cos = math.cos +local sin = math.sin +local m = mtul.math +local mat4 = mtul.math.mat4 + +local pitch_ZY = function(a) + local temp = mat4.new() + temp[6] = cos(a) + temp[7] = sin(a) + temp[10] = -sin(a) + temp[11] = cos(a) + return temp +end +local pitch_ZY2 = function(a) + local temp = mat4.new() + temp[6] = cos(a) + temp[7] = -sin(a) + temp[10] = sin(a) + temp[11] = cos(a) + return temp +end + +local roll_XY = function(a) + local temp = mat4.new() + temp[1] = cos(a) + temp[2] = sin(a) + temp[5] = -sin(a) + temp[6] = cos(a) + return temp +end +local roll_XY2 = function(a) + local temp = mat4.new() + temp[1] = cos(a) + temp[2] = -sin(a) + temp[5] = sin(a) + temp[6] = cos(a) + return temp +end +local yaw_ZX = function(a) + local temp = mat4.new() + temp[1] = cos(a) + temp[3] = -sin(a) + temp[9] = sin(a) + temp[11] = cos(a) + return temp +end +local yaw_ZX2 = function(a) + local temp = mat4.new() + temp[1] = cos(a) + temp[3] = sin(a) + temp[9] = -sin(a) + temp[11] = cos(a) + return temp +end +local pitch_transforms = { + pitch = pitch_ZY, + pitch_cw = pitch_ZY2, +} +local roll_transforms = { + roll = roll_XY, + roll_cw = roll_XY2 +} +local yaw_transforms = { + yaw = yaw_ZX, + yaw_cw = yaw_ZX2 +} +local possible_orders = { + {1,2,3}, + {1,3,2}, + + {2,3,1}, + {2,1,3}, + + {3,2,1}, + {3,1,2} +} +local matrix_tolerance = .00001 +local function check_matrix_equality(m1,m2) + for i = 1,16 do + if math.abs(m1[i]-m2[i]) > 0.001 then + return false + end + end + return true +end + +local function make_funcs_human_readable(str) + for i, v in pairs(pitch_transforms) do + str=string.gsub(str, tostring(v), i) + end + for i, v in pairs(roll_transforms) do + str=string.gsub(str, tostring(v), i ) + end + for i, v in pairs(yaw_transforms) do + str=string.gsub(str, tostring(v), i) + end + return str +end + +function mtul.math.find_matrix_rotation_order(check_func) + --x,y,z + local euler = {(math.random()-.5)*math.pi*4, (math.random()-.5)*math.pi*4, (math.random()-.5)*math.pi*4} + local output = check_func(mat4.new(), euler[1],euler[2],euler[3]) + local iter = 0 + local running_order + for _, p_tf in pairs(pitch_transforms) do + for _, y_tf in pairs(yaw_transforms) do + for _, r_tf in pairs(roll_transforms) do + --now that we have every combination, get every order of every combination. this is disusting by the way. + for _, order in pairs(possible_orders) do + iter = iter + 1 + --intrinsic order is pitch yaw roll for this check, meaning that 1 is assigned to pitch and so fourth. + local matrices = {p_tf, y_tf, r_tf} + local active_mat = mat4.new() + running_order = nil + for i=1,3 do + local func = matrices[order[i]] + running_order = (running_order and running_order .." * "..tostring(func)) or tostring(func) + active_mat = active_mat*func(euler[order[i]]) + end + --print("#"..iter, make_funcs_human_readable(running_order)) + if check_matrix_equality(output, active_mat) then + print(make_funcs_human_readable(running_order)) + --return true + end + end + end + end + end + return running_order +end +print("================== BEGINNING LUANTI AND IRRLICHT UNIT TESTs =======================") + +local find_rot_order = mtul.math.find_matrix_rotation_order +print("\n checking sanity of tests:") +local _tempeuler = {(math.random()-.5)*math.pi*4, (math.random()-.5)*math.pi*4, (math.random()-.5)*math.pi*4} +local _testmatrix = mtul.math.mat4.set_rot_zxy(mat4.new(), _tempeuler[1],_tempeuler[2],_tempeuler[3]) +print("matrix equality check func is sane:", check_matrix_equality(_testmatrix,_testmatrix)) +print("matrix equality check func tolerance:", matrix_tolerance) + +print("\n Checking rotation orders. Rotation application order is in reverse, these are the literal matrix multiplication order. ") +print("checking rotation matrix `set_rot_luanti_entity`") +find_rot_order(mtul.math.mat4.set_rot_luanti_entity) +print("checking `set_rot_irrlicht_bone`") +find_rot_order(mtul.math.mat4.set_rot_irrlicht_bone) + +print("================== ENDING LUANTI AND IRRLICHT UNIT TESTs =======================") diff --git a/unit_tests/matrix_unit_test.lua b/unit_tests/matrix_unit_test.lua new file mode 100644 index 0000000..17ad2d0 --- /dev/null +++ b/unit_tests/matrix_unit_test.lua @@ -0,0 +1,115 @@ +local mat4 = mtul.math.mat4 +local matrix_tolerance = .00001 +local function check_matrix_equality(m1,m2) + for i = 1,16 do + if math.abs(m1[i]-m2[i]) > 0.001 then + return false + end + end + return true +end + +local tau = math.pi*2 +local function santitize_angle(a) + if a > tau then + local co = math.floor(math.abs(a/tau)) + a = a-(co*tau) + end + if a < 0 then + local co = math.ceil(math.abs(a/tau)) + a = a+(co*tau) + end + return a +end +local function equals(a,b) + if math.abs(a-b) < .0001 then + return true + else + return false + end +end +local function santitize_angles_unpack(x,y,z) + return santitize_angle(x), santitize_angle(y), santitize_angle(z) +end +--[[for i=1,10 do + find_irr_order() +end]] +print("================== BEGINNING MATRIX UNIT TESTs =======================") +local find_rot_order = mtul.math.find_matrix_rotation_order +print("\n checking sanity of tests:") +local _tempeuler = {(math.random()-.5)*math.pi*4, (math.random()-.5)*math.pi*4, (math.random()-.5)*math.pi*4} +local _testmatrix = mtul.math.mat4.set_rot_zxy(mat4.new(), _tempeuler[1],_tempeuler[2],_tempeuler[3]) +print("matrix equality check func is sane:", check_matrix_equality(_testmatrix,_testmatrix)) +print("matrix equality check func tolerance:", matrix_tolerance) +local ran_ang = math.random()*math.pi*2 +print("santitize_angle is sane:", equals(1.60947655802, santitize_angle(7.8926618652)), equals(ran_ang, santitize_angle(ran_ang-tau))) +--print("checking irrlicht setRotationRadians") +--print(find_rot_order(irrlicht_matrix_setRotationRadians).." iterations") + +print("\n checking MTUL's luanti and irrlicht matrix rotation orders. Rotation application order is in reverse, these are the literal matrix multiplication order. ") +print("checking rotation matrix `set_rot_luanti_entity`") +find_rot_order(mtul.math.mat4.set_rot_luanti_entity) +print("checking `set_rot_irrlicht_bone`") +find_rot_order(mtul.math.mat4.set_rot_irrlicht_bone) + +--[[print("check in euler out euler for minetest entitiy matrix rotations") +local x,y,z =(math.random()-.5)*math.pi*4,(math.random()-.5)*math.pi*4,(math.random()-.5)*math.pi*4 +local new_mat = mat4.set_rot_luanti_entity(mat4.new(), x,y,z) +print(santitize_angles_unpack(x,y,z)) +print(santitize_angles_unpack(new_mat:get_rot_luanti_entity()))]] + + +--============================ ENTITY MATRICES ======================================= + +--random check to see if angles output correctly +print("\n Checking to euler and out euler. Verifying that `matrix1` and `matrix2` matches in `euler->matrix1->euler?->matrix2` for the following euler conversions") + +local x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +local new_mat = mat4.set_rot_luanti_entity(mat4.new(), x,y,z) +local x2,y2,z2 = new_mat:get_rot_luanti_entity() +print("luanti_entity (random angle) matrices are equivelant: ", check_matrix_equality(new_mat, mat4.set_rot_luanti_entity(mat4.new(), x2,y2,z2))) + +--repeat for irrlicht bones +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +new_mat = mat4.set_rot_irrlicht_bone(mat4.identity(), x,y,z) +x2,y2,z2 = new_mat:get_rot_irrlicht_bone() +print("irrlicht_bone (random angle) matrices are equivelant: ", check_matrix_equality(new_mat, mat4.set_rot_irrlicht_bone(mat4.new(), x2,y2,z2))) + +print("\n Checking edge cases for euler (where gimbal lock occours)") +--check if edge cases work properly +x,y,z = math.pi/2, math.random()*math.pi*2, math.random()*math.pi*2 +new_mat = mat4.set_rot_luanti_entity(mat4.identity(), x,y,z) +x2,y2,z2 = new_mat:get_rot_luanti_entity() +print("luanti_entity matrices are equivelant at `x=math.pi/2 or -math.pi/2:` ", check_matrix_equality(new_mat, mat4.set_rot_luanti_entity(mat4.new(), x2,y2,z2))) + +--check if edge cases work properly +x,y,z = math.random()*math.pi*2, math.pi/2, math.random()*math.pi*2 +new_mat = mat4.set_rot_irrlicht_bone(mat4.new(), x,y,z) +x2,y2,z2 = new_mat:get_rot_irrlicht_bone() +-- euler1->matrix->euler2; check euler1==euler2 +print("irrlicht_bone matrices are equivelant at `y=math.pi/2 or -math.pi/2`: ", check_matrix_equality(new_mat, mat4.set_rot_irrlicht_bone(mat4.new(), x2,y2,z2))) + +print("\n==================== END OF MATRIX UNIT TESTs =============================") + + +--[[local m00 = new_mat[1] +local m12 = new_mat[7] +local m22 = +local m02 = , , new_mat[11] +x = math.atan2(m12, m22); +y = math.atan2(-m02, math.sqrt(1.0 - m02 * m02)); +z = math.atan2(m01, m00); +print()]] + + + +--[[local quat = mtul.math.quat +print("\n comparing `euler to matrix` & `euler to quaternion` matrix outputs") +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +local mat1 = mat4.set_rot_zxy(mat4.new(), x,y,z) +local new_quat = quat.new():from_euler_zxy(x,y,z) +local mat2 = mat4.set_rot_from_quaternion(mat4.new(), new_quat) +--local new_quat = mtul.quat.from_euler_ +print(mat1) +print(mat2) +print(check_matrix_equality(mat1,mat2))]] \ No newline at end of file diff --git a/unit_tests/quat_unit_test.lua b/unit_tests/quat_unit_test.lua new file mode 100644 index 0000000..6201fdf --- /dev/null +++ b/unit_tests/quat_unit_test.lua @@ -0,0 +1,124 @@ + + +local mat4 = mtul.math.mat4 +local quat = mtul.math.quat +local vec3 = mtul.math.vec3 +local function check_matrix_equality(m1,m2) + for i = 1,16 do + if math.abs(m1[i]-m2[i]) > 0.001 then + return false + end + end + return true +end + +print("================== BEGINNING QUATERNION UNIT TESTs =======================") + +--print("\n comparing mul_vec3 and rotate_vec3 with random quat on forward facing unit dir") +--[[local new_quat = mtul.math.quat.from_angle_axis((math.random()-.5)*math.pi*4, mtul.math.vec3.new(math.random(), math.random(), math.random())):normalize() +local forward = vec3.new(0,0,1) +print(new_quat:mul_vec3(forward)) +print(new_quat:rotate_vec3(forward)) +print("identity quat:") +new_quat = quat.new(0,0,0,1) +print(new_quat:mul_vec3(forward)) +print(new_quat:rotate_vec3(forward))]] + + + +local new_quat = mtul.math.quat.from_angle_axis((math.random()-.5)*math.pi*4, mtul.math.vec3.new(math.random(), math.random(), math.random())):normalize() +local to_mat_from_quat = mtul.math.mat4.from_quaternion(new_quat) +local to_mat_from_axis_from_quat = mat4.from_angle_axis(new_quat:to_angle_axis()) +--should tell us that quat to matrix is working fine... trusting the original creators anyway... +print("\n comparing `quat->matrix` to old `quat->angle_axis->matrix`. Matches:", + check_matrix_equality( + to_mat_from_quat, --new mthod which generates a matrix + to_mat_from_axis_from_quat --old CPML method of from quaternion that just hooks through angle axis + ) +) +if not check_matrix_equality(to_mat_from_quat, to_mat_from_axis_from_quat) then + print(to_mat_from_quat) + print(to_mat_from_axis_from_quat) +end + +--double check (I dont trust the old method of converting to axis angle.) +local x = vec3.new(1,0,0); x=new_quat:mul_vec3(x) +local y = vec3.new(0,1,0); y=new_quat:mul_vec3(y) +local z = vec3.new(0,0,1); z=new_quat:mul_vec3(z) +local rotated_mat = mat4.new({ --this probably is confusing, each row her is a column. + x.x, x.y, x.z, 0, + y.x, y.y, y.z, 0, + z.x, z.y, z.z, 0, + 0, 0, 0, 1 +}) +local equal = check_matrix_equality(rotated_mat, to_mat_from_quat) +print("comparing matrix generated from rotating basis vectors to `quat->matrix`. Matches:", equal) +if not equal then + print(to_mat_from_quat) + print(rotated_mat) +end + +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +local matrix1 = mat4.set_rot_irrlicht_bone(mat4.identity(), x,y,z) --sample random matrix, what it is shouldn't matter as long as its special orthogonal (aka a rotation matrix) + +new_quat = mat4.to_quaternion(matrix1) --this is the independent variable in which we are testing- wether this code works. +local matrix2 = mat4.from_quaternion(new_quat) +print("checking `matrix1=matrix2` in `matrix1->quaternion->matrix2`. Matches:", check_matrix_equality(matrix1, matrix2)) +if not check_matrix_equality(matrix1, matrix2) then + print(matrix1) + print(matrix2) +end + +print("\n checking euler functions") +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +matrix1 = mat4.set_rot_luanti_entity(mat4.identity(), x,y,z) +new_quat = quat.from_matrix(matrix1) +local x2,y2,z2 = new_quat:get_euler_luanti_entity() +matrix2 = mat4.set_rot_luanti_entity(mat4.identity(), x2,y2,z2) + +print("(quat->euler) checking `matrix1=matrix2` in `euler->matrix1->quat->euler->matrix2 (ZXY/luanti entity)`. Matrices match:", check_matrix_equality(matrix1, matrix2)) +if not check_matrix_equality(matrix1, matrix2) then + print(matrix1) + print(matrix2) + print(x,y,z) + print(x2,y2,z2) +end + +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +matrix1 = mat4.set_rot_irrlicht_bone(mat4.identity(), x,y,z) +new_quat = quat.from_matrix(matrix1) +x2,y2,z2 = new_quat:get_euler_irrlicht_bone() +matrix2 = mat4.set_rot_irrlicht_bone(mat4.identity(), x2,y2,z2) + +print("(quat->euler) checking `matrix1=matrix2` in `euler->matrix1->quat->euler->matrix2 (XYZ/irrlicht bone)`. Matrices match:", check_matrix_equality(matrix1, matrix2)) +if not check_matrix_equality(matrix1, matrix2) then + print(matrix1) + print(matrix2) + print(x,y,z) + print(x2,y2,z2) +end + +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +matrix2 = mat4.set_rot_luanti_entity(mat4.identity(), x,y,z) +new_quat = quat.from_euler_luanti_entity(x,y,z) +matrix1 = mat4.from_quaternion(new_quat) +print("(euler->quat) checking `matrix1=matrix2` in `euler->quat->matrix1, euler->matrix2 (ZXY/luanti entity)`, Matches:", check_matrix_equality(matrix1, matrix2)) +if not check_matrix_equality(matrix1, matrix2) then + print(x,y,z) + print(matrix1) + print(matrix2) +end + +x,y,z = math.random()*math.pi*2, math.random()*math.pi*2, math.random()*math.pi*2 +matrix2 = mat4.set_rot_irrlicht_bone(mat4.identity(), x,y,z) +new_quat = quat.from_euler_irrlicht_bone(x,y,z) +matrix1 = mat4.from_quaternion(new_quat) +print("(euler->quat) checking `matrix1=matrix2` in `euler->quat->matrix1, euler->matrix2 (XYZ/irrlicht bone)`, Matches:", check_matrix_equality(matrix1, matrix2)) +if not check_matrix_equality(matrix1, matrix2) then + print(x,y,z) + print(matrix1) + print(matrix2) +end + +print("(eulur->quat->euler)") +print("\n==================== END OF QUATERNION UNIT TESTs =============================")