Skip to content

Commit 84edc2c

Browse files
committed
Add new 3x3 matrix cofactor() method and use for the normal matrix
It is both faster and correct when dealing with negative scalings that invert a model.
1 parent 7e98cf8 commit 84edc2c

File tree

7 files changed

+72
-17
lines changed

7 files changed

+72
-17
lines changed

src/flitter/model.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ cdef class Matrix33(Vector):
120120
cpdef Matrix33 copy(self)
121121
cdef Matrix33 mmul(self, Matrix33 b)
122122
cdef Vector vmul(self, Vector b)
123+
cpdef double det(self)
123124
cpdef Matrix33 inverse(self)
125+
cpdef Matrix33 cofactor(self)
124126
cpdef Matrix33 transpose(self)
125127
cpdef Matrix44 matrix44(self)
126128

@@ -172,6 +174,7 @@ cdef class Matrix44(Vector):
172174
cpdef Matrix44 inverse(self)
173175
cpdef Matrix44 transpose(self)
174176
cpdef Matrix33 inverse_transpose_matrix33(self)
177+
cpdef Matrix33 matrix33_cofactor(self)
175178
cpdef Matrix33 matrix33(self)
176179

177180

src/flitter/model.pyx

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,21 +1322,42 @@ cdef class Matrix33(Vector):
13221322
@cython.cdivision(True)
13231323
cpdef Matrix33 inverse(self):
13241324
cdef double* numbers = self.numbers
1325-
cdef double s0 = numbers[0] * (numbers[4]*numbers[8] - numbers[7]*numbers[5])
1326-
cdef double s1 = numbers[3] * (numbers[7]*numbers[2] - numbers[1]*numbers[8])
1327-
cdef double s2 = numbers[6] * (numbers[1]*numbers[5] - numbers[4]*numbers[2])
1328-
cdef double invdet = 1 / (s0 + s1 + s2)
1325+
cdef double invdet = 1 / self.det()
13291326
cdef Matrix33 result = Matrix33.__new__(Matrix33)
13301327
cdef double* result_numbers = result._numbers
1331-
result_numbers[0] = (numbers[4]*numbers[8] - numbers[7]*numbers[5]) * invdet
1328+
result_numbers[0] = (numbers[4]*numbers[8] - numbers[5]*numbers[7]) * invdet
13321329
result_numbers[1] = (numbers[2]*numbers[7] - numbers[1]*numbers[8]) * invdet
13331330
result_numbers[2] = (numbers[1]*numbers[5] - numbers[2]*numbers[4]) * invdet
13341331
result_numbers[3] = (numbers[5]*numbers[6] - numbers[3]*numbers[8]) * invdet
13351332
result_numbers[4] = (numbers[0]*numbers[8] - numbers[2]*numbers[6]) * invdet
1336-
result_numbers[5] = (numbers[3]*numbers[2] - numbers[0]*numbers[5]) * invdet
1337-
result_numbers[6] = (numbers[3]*numbers[7] - numbers[6]*numbers[4]) * invdet
1338-
result_numbers[7] = (numbers[6]*numbers[1] - numbers[0]*numbers[7]) * invdet
1339-
result_numbers[8] = (numbers[0]*numbers[4] - numbers[3]*numbers[1]) * invdet
1333+
result_numbers[5] = (numbers[2]*numbers[3] - numbers[0]*numbers[5]) * invdet
1334+
result_numbers[6] = (numbers[3]*numbers[7] - numbers[4]*numbers[6]) * invdet
1335+
result_numbers[7] = (numbers[1]*numbers[6] - numbers[0]*numbers[7]) * invdet
1336+
result_numbers[8] = (numbers[0]*numbers[4] - numbers[1]*numbers[3]) * invdet
1337+
result.numbers = result_numbers
1338+
result.length = 9
1339+
return result
1340+
1341+
cpdef double det(self):
1342+
cdef double* numbers = self.numbers
1343+
cdef double s0 = numbers[0] * (numbers[4]*numbers[8] - numbers[7]*numbers[5])
1344+
cdef double s1 = numbers[3] * (numbers[7]*numbers[2] - numbers[1]*numbers[8])
1345+
cdef double s2 = numbers[6] * (numbers[1]*numbers[5] - numbers[4]*numbers[2])
1346+
return s0 + s1 + s2
1347+
1348+
cpdef Matrix33 cofactor(self):
1349+
cdef double* numbers = self.numbers
1350+
cdef Matrix33 result = Matrix33.__new__(Matrix33)
1351+
cdef double* result_numbers = result._numbers
1352+
result_numbers[0] = numbers[4]*numbers[8] - numbers[5]*numbers[7]
1353+
result_numbers[1] = numbers[5]*numbers[6] - numbers[3]*numbers[8]
1354+
result_numbers[2] = numbers[3]*numbers[7] - numbers[4]*numbers[6]
1355+
result_numbers[3] = numbers[2]*numbers[7] - numbers[1]*numbers[8]
1356+
result_numbers[4] = numbers[0]*numbers[8] - numbers[2]*numbers[6]
1357+
result_numbers[5] = numbers[1]*numbers[6] - numbers[0]*numbers[7]
1358+
result_numbers[6] = numbers[1]*numbers[5] - numbers[2]*numbers[4]
1359+
result_numbers[7] = numbers[2]*numbers[3] - numbers[0]*numbers[5]
1360+
result_numbers[8] = numbers[0]*numbers[4] - numbers[1]*numbers[3]
13401361
result.numbers = result_numbers
13411362
result.length = 9
13421363
return result
@@ -1925,6 +1946,23 @@ cdef class Matrix44(Vector):
19251946
result.length = 9
19261947
return result
19271948

1949+
cpdef Matrix33 matrix33_cofactor(self):
1950+
cdef double* numbers = self.numbers
1951+
cdef Matrix33 result = Matrix33.__new__(Matrix33)
1952+
cdef double* result_numbers = result._numbers
1953+
result_numbers[0] = numbers[5]*numbers[10] - numbers[6]*numbers[9]
1954+
result_numbers[1] = numbers[6]*numbers[8] - numbers[4]*numbers[10]
1955+
result_numbers[2] = numbers[4]*numbers[9] - numbers[5]*numbers[8]
1956+
result_numbers[3] = numbers[2]*numbers[9] - numbers[1]*numbers[10]
1957+
result_numbers[4] = numbers[0]*numbers[10] - numbers[2]*numbers[8]
1958+
result_numbers[5] = numbers[1]*numbers[8] - numbers[0]*numbers[9]
1959+
result_numbers[6] = numbers[1]*numbers[6] - numbers[2]*numbers[5]
1960+
result_numbers[7] = numbers[2]*numbers[4] - numbers[0]*numbers[6]
1961+
result_numbers[8] = numbers[0]*numbers[5] - numbers[1]*numbers[4]
1962+
result.numbers = result_numbers
1963+
result.length = 9
1964+
return result
1965+
19281966
def __repr__(self):
19291967
cdef list rows = []
19301968
cdef double* numbers = self.numbers

src/flitter/render/window/canvas3d.pyx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ cdef class Camera:
313313
if up is None:
314314
camera.up = self.up
315315
else:
316-
camera.up = transform_matrix.inverse_transpose_matrix33().vmul(up).normalize()
316+
camera.up = transform_matrix.matrix33_cofactor().vmul(up).normalize()
317317
camera.fov = node.get_float('fov', self.fov)
318318
camera.fov_ref = node.get_str('fov_ref', self.fov_ref)
319319
camera.monochrome = node.get_bool('monochrome', self.monochrome)
@@ -555,7 +555,7 @@ cdef void collect(Node node, Matrix44 transform_matrix, Material material, Rende
555555
light.inner_cone = cos(inner * Pi)
556556
light.outer_cone = cos(outer * Pi)
557557
light.position = transform_matrix.vmul(position)
558-
light.direction = transform_matrix.inverse_transpose_matrix33().vmul(direction).normalize()
558+
light.direction = transform_matrix.matrix33_cofactor().vmul(direction).normalize()
559559
elif position.length:
560560
light.type = LightType.Point
561561
light.outer_cone = node.get_float('radius', 0)
@@ -564,7 +564,7 @@ cdef void collect(Node node, Matrix44 transform_matrix, Material material, Rende
564564
elif direction.as_bool():
565565
light.type = LightType.Directional
566566
light.position = None
567-
light.direction = transform_matrix.inverse_transpose_matrix33().vmul(direction).normalize()
567+
light.direction = transform_matrix.matrix33_cofactor().vmul(direction).normalize()
568568
else:
569569
light.type = LightType.Ambient
570570
light.position = None
@@ -804,7 +804,7 @@ cdef void render(render_target, RenderGroup render_group, Camera camera, glctx,
804804
dest = &instances_data[k, 0]
805805
for j in range(16):
806806
dest[j] = src[j]
807-
normal_matrix = instance.model_matrix.inverse_transpose_matrix33()
807+
normal_matrix = instance.model_matrix.matrix33_cofactor()
808808
src = normal_matrix.numbers
809809
dest = &instances_data[k, 16]
810810
for j in range(9):
@@ -853,7 +853,7 @@ cdef void render(render_target, RenderGroup render_group, Camera camera, glctx,
853853
dest = &instances_data[k, 0]
854854
for j in range(16):
855855
dest[j] = src[j]
856-
normal_matrix = instance.model_matrix.inverse_transpose_matrix33()
856+
normal_matrix = instance.model_matrix.matrix33_cofactor()
857857
src = normal_matrix.numbers
858858
dest = &instances_data[k, 16]
859859
for j in range(9):
@@ -905,7 +905,7 @@ cdef void render(render_target, RenderGroup render_group, Camera camera, glctx,
905905
dest = &instances_data[k, 0]
906906
for j in range(16):
907907
dest[j] = src[j]
908-
normal_matrix = instance.model_matrix.inverse_transpose_matrix33()
908+
normal_matrix = instance.model_matrix.matrix33_cofactor()
909909
src = normal_matrix.numbers
910910
dest = &instances_data[k, 16]
911911
for j in range(9):

src/flitter/render/window/models.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ cdef class Trim(UnaryOperation):
669669

670670
cdef Model _transform(self, Matrix44 transform_matrix):
671671
return self.original._transform(transform_matrix)._trim(transform_matrix.vmul(self.origin),
672-
transform_matrix.inverse_transpose_matrix33().vmul(self.normal),
672+
transform_matrix.matrix33_cofactor().vmul(self.normal).normalize(),
673673
self.smooth, self.fillet, self.chamfer)
674674

675675
cpdef double signed_distance(self, double x, double y, double z) noexcept:

tests/test_matrix33.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ def test_inverse(self):
9191
self.assertAllAlmostEqual((((b.inverse() @ a.inverse()) @ a) @ b) @ c, c)
9292
self.assertAllAlmostEqual(Matrix33([1, 3, -1, 2, 4, 2, 1, 1, -1]).inverse(), [-0.75, 0.25, 1.25, 0.5, 0, -0.5, -0.25, 0.25, -0.25])
9393

94+
def test_cofactor(self):
95+
m = Matrix33.scale([2, -3]) @ \
96+
Matrix33.rotate(1/3) @ \
97+
Matrix33.translate([7, 9])
98+
self.assertAllAlmostEqual(m @ m.cofactor().transpose(), Matrix33(m.det()))
99+
94100
def test_transpose(self):
95101
self.assertEqual(Matrix33().transpose(), Matrix33())
96102
self.assertEqual(Matrix33(range(9)).transpose(), [0, 3, 6, 1, 4, 7, 2, 5, 8])

tests/test_matrix44.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ def test_inverse_transpose_matrix33(self):
216216
Matrix44.scale([5, 6, -3])
217217
self.assertAllAlmostEqual(m.inverse_transpose_matrix33(), m.inverse().transpose().matrix33())
218218

219+
def test_matrix33_cofactor(self):
220+
m = Matrix44.look([1, 3, 3], [1, 2, 3], [-1, 0, 0]) @ \
221+
Matrix44.scale([5, 6, -3])
222+
self.assertAllAlmostEqual(m.matrix33_cofactor(), m.matrix33().cofactor())
223+
self.assertAllAlmostEqual((m.matrix33_cofactor() @ [1, 0, 0]).normalize(), [0, 1, 0])
224+
self.assertAllAlmostEqual((m.matrix33_cofactor() @ [0, 1, 0]).normalize(), [0, 0, -1])
225+
self.assertAllAlmostEqual((m.matrix33_cofactor() @ [0, 0, 1]).normalize(), [-1, 0, 0])
226+
219227
def test_repr(self):
220228
self.assertEqual(repr(Matrix44()), """| 1.000 0.000 0.000 0.000 |
221229
| 0.000 1.000 0.000 0.000 |

tests/test_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ def test_trim(self):
381381
self.assertEqual(Model.box().trim(self.P, self.N).invert().name, f'invert(trim(!box, {self.PN_hash}))')
382382
self.assertEqual(Model.box().trim(self.P, self.N).repair().name, f'trim(repair(!box), {self.PN_hash})')
383383
self.assertEqual(Model.box().trim(self.P, self.N).snap_edges().name, f'snap_edges(trim(!box, {self.PN_hash}))')
384-
MPN_hash = hex((self.M @ self.P).hash(False) ^ (self.M.inverse_transpose_matrix33() @ self.N).hash(False))[2:]
384+
MPN_hash = hex((self.M @ self.P).hash(False) ^ ((self.M.matrix33_cofactor() @ self.N).normalize()).hash(False))[2:]
385385
self.assertEqual(Model.box().trim(self.P, self.N).transform(self.M).name, f'trim(!box@{self.M_hash}, {MPN_hash})')
386386
self.assertEqual(Model.box().trim(self.P, self.N).uv_remap('sphere').name, f'uv_remap(trim(!box, {self.PN_hash}), sphere)')
387387
self.assertEqual(Model.box().trim(self.P, self.N).trim(self.P, self.N).name, f'trim(trim(!box, {self.PN_hash}), {self.PN_hash})')

0 commit comments

Comments
 (0)