ncca-ngl 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ncca/ngl/mat3.py ADDED
@@ -0,0 +1,466 @@
1
+ """
2
+ Simple Mat3 class which can be used with the Vec3 class. By default it will be set to the identity matrix
3
+
4
+ all matrix values are stored in a 3x3 list in the format
5
+
6
+ .. highlight:: python
7
+ .. code-block:: python
8
+
9
+ m=[[1.0,0.0,0.0],
10
+ [0.0,1.0,0.0],
11
+ [0.0,1.0,0.0]]
12
+
13
+ """
14
+
15
+ import copy
16
+ import functools
17
+ import math
18
+ import operator
19
+
20
+ import numpy as np
21
+
22
+
23
+ class Mat3Error(Exception):
24
+ """This exception will be raised if we have issues with matrix multiplication or other mathematical operations"""
25
+
26
+ pass
27
+
28
+
29
+ class Mat3NotSquare(Exception):
30
+ """If we try to construct from a non square (3x3) value or 9 elements this exception will be thrown"""
31
+
32
+ pass
33
+
34
+
35
+ _identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
36
+
37
+
38
+ class Mat3:
39
+ """Simple Mat3 class for basic affine transforms"""
40
+
41
+ __slots__ = ["m"]
42
+ """m : list
43
+ the matrix values
44
+ """
45
+
46
+ def __init__(self):
47
+ """construct to identity matrix"""
48
+ self.m = copy.deepcopy(_identity)
49
+
50
+ def get_matrix(self):
51
+ """return matrix elements as list ideal for OpenGL
52
+
53
+ Returns
54
+ -------
55
+ list
56
+ the 9 float elements of the matrix, ideal
57
+ for OpenGL or Renderman consumption
58
+ """
59
+
60
+ return functools.reduce(operator.concat, self.m)
61
+
62
+ def to_numpy(self):
63
+ "return matrix as a numpy array ideal for WebGPU etc"
64
+ return np.array(self.get_matrix()).reshape([3, 3])
65
+
66
+ @classmethod
67
+ def identity(cls):
68
+ """class method to return a new identity matrix
69
+
70
+ Returns
71
+ -------
72
+ Mat3
73
+ new Mat3 matrix as Identity
74
+
75
+ """
76
+ v = Mat3()
77
+ return v
78
+
79
+ @classmethod
80
+ def zero(cls):
81
+ """class method to return a new zero matrix
82
+
83
+ Returns
84
+ -------
85
+ Mat3
86
+ new Mat3 matrix as all zeros
87
+
88
+ """
89
+ v = Mat3()
90
+ v.m = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
91
+ return v
92
+
93
+ @classmethod
94
+ def from_list(cls, lst):
95
+ """class method to create Mat3 from list
96
+
97
+ Parameters
98
+ ----------
99
+ lst : list
100
+
101
+ list of 9 numbers to construct a new Mat3 from, we will accept either 9 floats or 3 lists of size 3
102
+
103
+ .. highlight:: python
104
+ .. code-block:: python
105
+
106
+ a=Mat3.from_list([1,2,3,4,5,6,7,8,9])
107
+ b=Mat3.from_list([1,2,3],[4,5,6],[7,8,9])
108
+
109
+ Returns
110
+ -------
111
+ Mat3
112
+ new Mat3 from list elements
113
+
114
+ :raises:
115
+ Mat3NotSquare : if we don't get a 3x3 we raise this
116
+
117
+ """
118
+ v = Mat3()
119
+ v.m = lst
120
+ if not v._is_square():
121
+ if len(lst) == 9: # can convert
122
+ v.m = [lst[0:3], lst[3:6], lst[6:]]
123
+ return v
124
+ else:
125
+ raise Mat3NotSquare
126
+ else:
127
+ return v
128
+
129
+ def _is_square(self) -> bool:
130
+ """ensure matrix is square"""
131
+ return len(self.m) == 3 and all(len(i) == 3 for i in self.m)
132
+
133
+ def transpose(self):
134
+ """transpose this matrix"""
135
+ self.m = [list(item) for item in zip(*self.m)]
136
+
137
+ def get_transpose(self):
138
+ """return a new matrix as the transpose of ourself
139
+
140
+ Returns
141
+ -------
142
+ Mat3
143
+ The transpose of the current matrix
144
+ """
145
+ m = Mat3()
146
+ m.m = [list(item) for item in zip(*self.m)]
147
+ return m
148
+
149
+ @classmethod
150
+ def scale(cls, x: float, y: float, z: float):
151
+ """return a scale matrix resetting to identity first
152
+
153
+ Parameters
154
+ ----------
155
+ x : float
156
+ uniform scale in the x axis
157
+ y : float
158
+ uniform scale in the y axis
159
+ z : float
160
+ uniform scale in the z axis
161
+
162
+ .. highlight:: python
163
+ .. code-block:: python
164
+
165
+ scale=Mat3.scale(2.0,1.0,3.0)
166
+
167
+ Returns
168
+ -------
169
+ Mat3
170
+ matrix with diagonals set to the scale
171
+
172
+ """
173
+ s = Mat3()
174
+ s.m[0][0] = x
175
+ s.m[1][1] = y
176
+ s.m[2][2] = z
177
+ return s
178
+
179
+ @classmethod
180
+ def rotate_x(cls, angle: float):
181
+ """return a matrix as the rotation around the Cartesian X axis by angle degrees
182
+
183
+ Parameters
184
+ ----------
185
+
186
+ angle : float
187
+ the angle in degrees to rotate
188
+
189
+ .. highlight:: python
190
+ .. code-block:: python
191
+
192
+ rotx=Mat3.rotateX(45.0)
193
+
194
+ Returns
195
+ -------
196
+ Mat3
197
+ rotation matrix
198
+ """
199
+ x = Mat3()
200
+ beta = math.radians(angle)
201
+ sr = math.sin(beta)
202
+ cr = math.cos(beta)
203
+ x.m[1][1] = cr
204
+ x.m[1][2] = sr
205
+ x.m[2][1] = -sr
206
+ x.m[2][2] = cr
207
+ return x
208
+
209
+ @classmethod
210
+ def rotate_y(cls, angle: float):
211
+ """return a matrix as the rotation around the Cartesian Y axis by angle degrees
212
+
213
+ Parameters
214
+ ----------
215
+
216
+ angle : float
217
+ the angle in degrees to rotate
218
+
219
+ .. highlight:: python
220
+ .. code-block:: python
221
+
222
+ roty=Mat3.rotateY(45.0)
223
+
224
+ Returns
225
+ -------
226
+ Mat3
227
+ rotation matrix
228
+ """
229
+ y = Mat3()
230
+ beta = math.radians(angle)
231
+ sr = math.sin(beta)
232
+ cr = math.cos(beta)
233
+ y.m[0][0] = cr
234
+ y.m[0][2] = -sr
235
+ y.m[2][0] = sr
236
+ y.m[2][2] = cr
237
+ return y
238
+
239
+ @classmethod
240
+ def rotate_z(cls, angle: float):
241
+ """return a matrix as the rotation around the Cartesian Z axis by angle degrees
242
+
243
+ Parameters
244
+ ----------
245
+
246
+ angle : float
247
+ the angle in degrees to rotate
248
+
249
+ .. highlight:: python
250
+ .. code-block:: python
251
+
252
+ rotz=Mat3.rotateZ(45.0)
253
+
254
+ Returns
255
+ -------
256
+ Mat3
257
+ rotation matrix
258
+ """
259
+ z = Mat3()
260
+ beta = math.radians(angle)
261
+ sr = math.sin(beta)
262
+ cr = math.cos(beta)
263
+ z.m[0][0] = cr
264
+ z.m[0][1] = sr
265
+ z.m[1][0] = -sr
266
+ z.m[1][1] = cr
267
+ return z
268
+
269
+ def __getitem__(self, idx):
270
+ """access array elements remember this is a list of lists [[3],[3],[3]]
271
+ Parameters
272
+ ----------
273
+ idx : int
274
+ the index into the list to get the sub list
275
+
276
+
277
+ """
278
+ return self.m[idx]
279
+
280
+ def __setitem__(self, idx, item):
281
+ """set array elements remember this is a list of lists [[3],[3],[3]]
282
+
283
+ Parameters
284
+ ----------
285
+ idx : int
286
+ the index into the list to set the sub list
287
+ """
288
+ self.m[idx] = item
289
+
290
+ def __mul__(self, rhs):
291
+ """Multiply matrix by scalar
292
+
293
+ Parameters
294
+ __________
295
+ rhs : float int
296
+ multiply each matrix element by rhs
297
+
298
+ raises : Mat3Error
299
+ if rhs is not a number
300
+ """
301
+ if isinstance(rhs, (int, float)):
302
+ for i in range(0, len(self.m)):
303
+ for j in range(0, len(self.m[i])):
304
+ self.m[i][j] *= rhs
305
+ return self
306
+ raise Mat3Error
307
+
308
+ def _mat_mul(self, rhs):
309
+ "matrix mult internal function"
310
+ # fmt: off
311
+ a00 = self.m[0][0] # cache values for speed? (works in C++ not sure about python)
312
+ a01 = self.m[0][1]
313
+ a02 = self.m[0][2]
314
+ a10 = self.m[1][0]
315
+ a11 = self.m[1][1]
316
+ a12 = self.m[1][2]
317
+ a20 = self.m[2][0]
318
+ a21 = self.m[2][1]
319
+ a22 = self.m[2][2]
320
+
321
+ b00 = rhs.m[0][0]
322
+ b01 = rhs.m[0][1]
323
+ b02 = rhs.m[0][2]
324
+ b10 = rhs.m[1][0]
325
+ b11 = rhs.m[1][1]
326
+ b12 = rhs.m[1][2]
327
+ b20 = rhs.m[2][0]
328
+ b21 = rhs.m[2][1]
329
+ b22 = rhs.m[2][2]
330
+
331
+ ret=Mat3() # result mat4
332
+ ret.m[0][0] = b00 * a00 + b01 * a10 + b02 * a20
333
+ ret.m[0][1] = b00 * a01 + b01 * a11 + b02 * a21
334
+ ret.m[0][2] = b00 * a02 + b01 * a12 + b02 * a22
335
+ ret.m[1][0] = b10 * a00 + b11 * a10 + b12 * a20
336
+ ret.m[1][1] = b10 * a01 + b11 * a11 + b12 * a21
337
+ ret.m[1][2] = b10 * a02 + b11 * a12 + b12 * a22
338
+ ret.m[2][0] = b20 * a00 + b21 * a10 + b22 * a20
339
+ ret.m[2][1] = b20 * a01 + b21 * a11 + b22 * a21
340
+ ret.m[2][2] = b20 * a02 + b21 * a12 + b22 * a22
341
+ return ret
342
+ # fmt: on
343
+
344
+ def __matmul__(self, rhs):
345
+ "multiply matrix by another matrix"
346
+ from .vec3 import Vec3 # note relative import
347
+
348
+ if isinstance(rhs, Mat3):
349
+ return self._mat_mul(rhs)
350
+ elif isinstance(rhs, Vec3):
351
+ return Vec3(
352
+ rhs.x * self.m[0][0] + rhs.y * self.m[0][1] + rhs.z * self.m[0][2],
353
+ rhs.x * self.m[1][0] + rhs.y * self.m[1][1] + rhs.z * self.m[1][2],
354
+ rhs.x * self.m[2][0] + rhs.y * self.m[2][1] + rhs.z * self.m[2][2],
355
+ )
356
+ else:
357
+ raise Mat3Error
358
+
359
+ def _add(self, rhs):
360
+ "internal add function"
361
+ temp = Mat3()
362
+ for i in range(0, len(temp.m)):
363
+ temp.m[i] = [a + b for a, b in zip(self.m[i], rhs.m[i])]
364
+ return temp
365
+
366
+ def __add__(self, rhs):
367
+ "piecewise addition of elements"
368
+ return self._add(rhs)
369
+
370
+ def __iadd__(self, rhs):
371
+ "piecewise addition of elements to this"
372
+ return self._add(rhs)
373
+
374
+ def _sub(self, rhs):
375
+ "internal subtract function"
376
+ temp = Mat3()
377
+ for i in range(0, len(temp.m)):
378
+ temp.m[i] = [a - b for a, b in zip(self.m[i], rhs.m[i])]
379
+ return temp
380
+
381
+ def __sub__(self, rhs):
382
+ "piecewise subtraction of elements"
383
+ return self._sub(rhs)
384
+
385
+ def __isub__(self, rhs):
386
+ "piecewise subtraction of elements to this"
387
+ return self._sub(rhs)
388
+
389
+ def determinant(self):
390
+ "determinant of matrix"
391
+ return (
392
+ +self.m[0][0] * (self.m[1][1] * self.m[2][2] - self.m[2][1] * self.m[1][2])
393
+ - self.m[0][1] * (self.m[1][0] * self.m[2][2] - self.m[1][2] * self.m[2][0])
394
+ + self.m[0][2] * (self.m[1][0] * self.m[2][1] - self.m[1][1] * self.m[2][0])
395
+ )
396
+
397
+ def to_list(self):
398
+ "convert matrix to list"
399
+ # flatten to single array
400
+ return functools.reduce(operator.concat, self.m)
401
+
402
+ def inverse(self):
403
+ "Inverse of matrix raise MatrixError if not calculable"
404
+ det = self.determinant()
405
+ try:
406
+ invdet = 1 / det
407
+ tmp = Mat3()
408
+ # minor matrix + co-factor
409
+ tmp.m[0][0] = (
410
+ +(self.m[1][1] * self.m[2][2] - self.m[1][2] * self.m[2][1]) * invdet
411
+ )
412
+ tmp.m[1][0] = (
413
+ -(self.m[1][0] * self.m[2][2] - self.m[1][2] * self.m[2][0]) * invdet
414
+ )
415
+ tmp.m[2][0] = (
416
+ +(self.m[1][0] * self.m[2][1] - self.m[1][1] * self.m[2][0]) * invdet
417
+ )
418
+
419
+ tmp.m[0][1] = (
420
+ -(self.m[0][1] * self.m[2][2] - self.m[0][2] * self.m[2][1]) * invdet
421
+ )
422
+ tmp.m[1][1] = (
423
+ +(self.m[0][0] * self.m[2][2] - self.m[0][2] * self.m[2][0]) * invdet
424
+ )
425
+ tmp.m[2][1] = (
426
+ -(self.m[0][0] * self.m[2][1] - self.m[0][1] * self.m[2][0]) * invdet
427
+ )
428
+
429
+ tmp.m[0][2] = (
430
+ +(self.m[0][1] * self.m[1][2] - self.m[0][2] * self.m[1][1]) * invdet
431
+ )
432
+ tmp.m[1][2] = (
433
+ -(self.m[0][0] * self.m[1][2] - self.m[0][2] * self.m[1][0]) * invdet
434
+ )
435
+ tmp.m[2][2] = (
436
+ +(self.m[0][0] * self.m[1][1] - self.m[0][1] * self.m[1][0]) * invdet
437
+ )
438
+
439
+ return tmp
440
+ except ZeroDivisionError:
441
+ raise Mat3Error
442
+
443
+ def __str__(self):
444
+ """return string representation"""
445
+ return f"[{self.m[0]}\n{self.m[1]}\n{self.m[2]}]"
446
+
447
+ def __repr__(self):
448
+ """return string representation"""
449
+ return f"Mat3({self.m})"
450
+
451
+ @classmethod
452
+ def from_mat4(cls, mat4):
453
+ """Create a Mat3 from a Mat4"""
454
+ return Mat3.from_list(
455
+ [
456
+ mat4.m[0][0],
457
+ mat4.m[0][1],
458
+ mat4.m[0][2],
459
+ mat4.m[1][0],
460
+ mat4.m[1][1],
461
+ mat4.m[1][2],
462
+ mat4.m[2][0],
463
+ mat4.m[2][1],
464
+ mat4.m[2][2],
465
+ ]
466
+ )