1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
import pytest
from pyglet.math import Mat3, Mat4, Vec3
@pytest.fixture()
def mat3():
return Mat3()
@pytest.fixture()
def mat4():
return Mat4()
##################
# helper functions
##################
def inverse(matrix):
"""The inverse of a Matrix.
Using Gauss-Jordan elimination, the matrix supplied is transformed into
the identity matrix using a sequence of elementary row operations (below).
The same sequence of operations is applied to the identity matrix,
transforming it into the supplied matrix's inverse.
Elementary row operations:
- multiply row by a scalar
- swap two rows
- add two rows together"""
i = Mat4() # identity matrix
# The pivot in each column is the element at Matrix[c, c] (diagonal elements).
# The pivot row is the row containing the pivot element. Pivot elements must
# be non-zero.
# Any time matrix is changed, so is i.
for c in range(4):
# Find and swap pivot row into place
if matrix[4*c + c] == 0:
for r in range(c + 1, 4):
if matrix[4*r + c] != 0:
matrix = _row_swap(matrix, c, r)
i = _row_swap(i, c, r)
# Make 0's in column for rows that aren't pivot row
for r in range(4):
if r != c: # not the pivot row
r_piv = matrix[4*r + c]
if r_piv != 0:
piv = matrix[4*c + c]
scalar = r_piv / piv
matrix = _row_mul(matrix, c, scalar)
matrix = row_sub(matrix, c, r)
i = _row_mul(i, c, scalar)
i = row_sub(i, c, r)
# Put matrix in reduced row-echelon form.
piv = matrix[4*c + c]
matrix = _row_mul(matrix, c, 1 / piv)
i = _row_mul(i, c, 1 / piv)
return i
def _row_swap(matrix, r1, r2):
lo = min(r1, r2)
hi = max(r1, r2)
values = (matrix[:lo*4]
+ matrix[hi*4:hi*4 + 4]
+ matrix[lo*4 + 4:hi*4]
+ matrix[lo*4:lo*4 + 4]
+ matrix[hi*4 + 4:])
return Mat4(values)
def _row_mul(matrix, r, x):
values = (matrix[:r*4]
+ tuple(v * x for v in matrix[r*4:r*4 + 4])
+ matrix[r*4 + 4:])
return Mat4(values)
# subtracts r1 from r2
def row_sub(matrix, r1, r2):
row1 = matrix[4*r1:4*r1 + 4]
row2 = matrix[4*r2:4*r2 + 4]
values = (matrix[:r2*4]
+ tuple(v2 - v1 for (v1, v2) in zip(row1, row2))
+ matrix[r2*4 + 4:])
return matrix.Mat4(values)
############
# test cases
############
def test_mat3_creation(mat3):
assert len(mat3) == 9
assert mat3 == (1, 0, 0,
0, 1, 0,
0, 0, 1)
def test_mat3_creation_from_list(mat3):
mat3_from_list = Mat3([1, 0, 0,
0, 1, 0,
0, 0, 1])
assert mat3_from_list == mat3
def test_mat4_creation(mat4):
assert len(mat4) == 16
assert mat4 == (1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1)
def test_mat4_creation_from_list(mat4):
mat4_from_list = Mat4([1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1])
assert mat4_from_list == mat4
def test_mat4_inversion(mat4):
# Confirm `__invert__` method matches long hand utility method:
inverse_1 = inverse(mat4)
inverse_2 = ~mat4
assert round(inverse_1, 9) == round(inverse_2, 9)
# Confirm that Matrix @ its inverse == identity Matrix:
assert round(mat4 @ inverse_1, 9) == Mat4()
assert round(mat4 @ inverse_2, 9) == Mat4()
def test_mat3_associative_mul():
swap_xy = Mat3((0,1,0, 1,0,0, 0,0,1))
scale_x = Mat3((2,0,0, 0,1,0, 0,0,1))
v1 = (swap_xy @ scale_x) @ Vec3(0,1,0)
v2 = swap_xy @ (scale_x @ Vec3(0,1,0))
assert v1 == v2 and abs(v1) != 0
|