matrix44.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. # original code from package: gameobjects
  2. # Home-page: http://code.google.com/p/gameobjects/
  3. # Author: Will McGugan
  4. # Download-URL: http://code.google.com/p/gameobjects/downloads/list
  5. # Created: 19.04.2010
  6. # Copyright (c) 2010-2018 Manfred Moitzi
  7. # License: MIT License
  8. from typing import Sequence, Iterable, List, Tuple, TYPE_CHECKING
  9. from math import sin, cos, tan
  10. from itertools import chain
  11. if TYPE_CHECKING:
  12. from ezdxf.eztypes import Vertex
  13. Tuple4Float = Tuple[float, float, float, float]
  14. # removed array.array because array is optimized for space not speed, and space optimization is not needed
  15. def floats(items: Iterable) -> List[float]:
  16. return [float(v) for v in items]
  17. class Matrix44:
  18. _identity = (
  19. 1.0, 0.0, 0.0, 0.0,
  20. 0.0, 1.0, 0.0, 0.0,
  21. 0.0, 0.0, 1.0, 0.0,
  22. 0.0, 0.0, 0.0, 1.0
  23. )
  24. __slots__ = ('matrix',)
  25. def __init__(self, *args):
  26. """
  27. Matrix44() is the identity matrix.
  28. Matrix44(values) values is an iterable with the 16 components of the matrix.
  29. Matrix44(row1, row2, row3, row4) four rows, each row with four values.
  30. """
  31. self.matrix = None # type: List
  32. self.set(*args)
  33. def set(self, *args) -> None:
  34. """
  35. Reset matrix values.
  36. set() creates the identity matrix.
  37. set(values) values is an iterable with the 16 components of the matrix.
  38. set(row1, row2, row3, row4) four rows, each row with four values.
  39. """
  40. nargs = len(args)
  41. if nargs == 0:
  42. self.matrix = floats(Matrix44._identity)
  43. elif nargs == 1:
  44. self.matrix = floats(args[0])
  45. elif nargs == 4:
  46. self.matrix = floats(chain(*args))
  47. else:
  48. raise ValueError("Invalid count of arguments (4 row vectors or one list with 16 values).")
  49. if len(self.matrix) != 16:
  50. raise ValueError("Invalid matrix count")
  51. def __repr__(self) -> str:
  52. def format_row(row):
  53. return "(%s)" % ", ".join(str(value) for value in row)
  54. return "Matrix44(%s)" % \
  55. ", ".join(format_row(row) for row in self.rows())
  56. def get_row(self, row: int) -> Tuple4Float:
  57. index = row * 4
  58. return tuple(self.matrix[index:index + 4])
  59. def set_row(self, row: int, values: Sequence[float]) -> None:
  60. index = row * 4
  61. self.matrix[index:index + len(values)] = floats(values)
  62. def get_col(self, col: int) -> Tuple4Float:
  63. """
  64. Returns a column as a tuple of four floats.
  65. """
  66. m = self.matrix
  67. return m[col], m[col + 4], m[col + 8], m[col + 12]
  68. def set_col(self, col: int, values: Sequence[float]):
  69. """
  70. Sets the values in a column.
  71. """
  72. m = self.matrix
  73. a, b, c, d = values
  74. m[col] = float(a)
  75. m[col + 4] = float(b)
  76. m[col + 8] = float(c)
  77. m[col + 12] = float(d)
  78. def copy(self) -> 'Matrix44':
  79. return self.__class__(self.matrix)
  80. __copy__ = copy
  81. @classmethod
  82. def scale(cls, sx: float, sy: float = None, sz: float = None) -> 'Matrix44':
  83. """
  84. Returns a scaling transformation matrix. If sy is None, sy = sx, and if sz is None sz = sx.
  85. """
  86. if sy is None:
  87. sy = sx
  88. if sz is None:
  89. sz = sx
  90. return cls([
  91. float(sx), 0., 0., 0.,
  92. 0., float(sy), 0., 0.,
  93. 0., 0., float(sz), 0.,
  94. 0., 0., 0., 1.
  95. ])
  96. @classmethod
  97. def translate(cls, x: float, y: float, z: float) -> 'Matrix44':
  98. """
  99. Returns a translation matrix to (x, y, z).
  100. """
  101. return cls([
  102. 1., 0., 0., 0.,
  103. 0., 1., 0., 0.,
  104. 0., 0., 1., 0.,
  105. float(x), float(y), float(z), 1.
  106. ])
  107. @classmethod
  108. def x_rotate(cls, angle: float) -> 'Matrix44':
  109. """
  110. Returns a rotation matrix about the x-axis.
  111. Args:
  112. angle: rotation angle in radians
  113. """
  114. cos_a = cos(angle)
  115. sin_a = sin(angle)
  116. return cls([
  117. 1., 0., 0., 0.,
  118. 0., cos_a, sin_a, 0.,
  119. 0., -sin_a, cos_a, 0.,
  120. 0., 0., 0., 1.
  121. ])
  122. @classmethod
  123. def y_rotate(cls, angle: float) -> 'Matrix44':
  124. """
  125. Returns a rotation matrix about the y-axis.
  126. Args:
  127. angle: rotation angle in radians
  128. """
  129. cos_a = cos(angle)
  130. sin_a = sin(angle)
  131. return cls([
  132. cos_a, 0., -sin_a, 0.,
  133. 0., 1., 0., 0.,
  134. sin_a, 0., cos_a, 0.,
  135. 0., 0., 0., 1.
  136. ])
  137. @classmethod
  138. def z_rotate(cls, angle: float) -> 'Matrix44':
  139. """
  140. Returns a rotation matrix about the z-axis.
  141. Args:
  142. angle: rotation angle in radians
  143. """
  144. cos_a = cos(angle)
  145. sin_a = sin(angle)
  146. return cls([
  147. cos_a, sin_a, 0., 0.,
  148. -sin_a, cos_a, 0., 0.,
  149. 0., 0., 1., 0.,
  150. 0., 0., 0., 1.
  151. ])
  152. @classmethod
  153. def axis_rotate(cls, axis: 'Vertex', angle: float) -> 'Matrix44':
  154. """
  155. Returns a rotation matrix about an arbitrary axis.
  156. Args:
  157. axis: rotation axis as (x, y, z) tuple
  158. angle: rotation angle in radians
  159. """
  160. c = cos(angle)
  161. s = sin(angle)
  162. omc = 1. - c
  163. x, y, z = axis
  164. return cls([
  165. x * x * omc + c, y * x * omc + z * s, x * z * omc - y * s, 0.,
  166. x * y * omc - z * s, y * y * omc + c, y * z * omc + x * s, 0.,
  167. x * z * omc + y * s, y * z * omc - x * s, z * z * omc + c, 0.,
  168. 0., 0., 0., 1.
  169. ])
  170. @classmethod
  171. def xyz_rotate(cls, angle_x: float, angle_y: float, angle_z: float) -> 'Matrix44':
  172. """
  173. Returns a rotation matrix for rotation about each axis.
  174. Args:
  175. angle_x: rotation angle about x-axis in radians
  176. angle_y: rotation angle about y-axis in radians
  177. angle_z: rotation angle about z-axis in radians
  178. """
  179. cx = cos(angle_x)
  180. sx = sin(angle_x)
  181. cy = cos(angle_y)
  182. sy = sin(angle_y)
  183. cz = cos(angle_z)
  184. sz = sin(angle_z)
  185. sxsy = sx * sy
  186. cxsy = cx * sy
  187. return cls([
  188. cy * cz, sxsy * cz + cx * sz, -cxsy * cz + sx * sz, 0.,
  189. -cy * sz, -sxsy * sz + cx * cz, cxsy * sz + sx * cz, 0.,
  190. sy, -sx * cy, cx * cy, 0.,
  191. 0., 0., 0., 1.])
  192. @classmethod
  193. def perspective_projection(cls, left: float, right: float, top: float, bottom: float, near: float,
  194. far: float) -> 'Matrix44':
  195. """
  196. Returns a matrix for a 2d projection.
  197. Args:
  198. left: Coordinate of left of screen
  199. right: Coordinate of right of screen
  200. top: Coordinate of the top of the screen
  201. bottom: Coordinate of the bottom of the screen
  202. near: Coordinate of the near clipping plane
  203. far: Coordinate of the far clipping plane
  204. """
  205. return cls([
  206. (2. * near) / (right - left), 0., 0., 0.,
  207. 0., (2. * near) / (top - bottom), 0., 0.,
  208. (right + left) / (right - left), (top + bottom) / (top - bottom), -((far + near) / (far - near)), -1.,
  209. 0., 0., -((2. * far * near) / (far - near)), 0.
  210. ])
  211. @classmethod
  212. def perspective_projection_fov(cls, fov: float, aspect: float, near: float, far: float) -> 'Matrix44':
  213. """
  214. Returns a matrix for a 2d projection.
  215. Args:
  216. fov: The field of view (in radians)
  217. aspect: The aspect ratio of the screen (width / height)
  218. near: Coordinate of the near clipping plane
  219. far: Coordinate of the far clipping plane
  220. """
  221. vrange = near * tan(fov / 2.)
  222. left = -vrange * aspect
  223. right = vrange * aspect
  224. bottom = -vrange
  225. top = vrange
  226. return cls.perspective_projection(left, right, bottom, top, near, far)
  227. @staticmethod
  228. def chain(*matrices: Iterable['Matrix44']) -> 'Matrix44':
  229. """
  230. Compose a transformation matrix from one or more matrices.
  231. """
  232. transformation = Matrix44()
  233. for matrix in matrices:
  234. transformation *= matrix
  235. return transformation
  236. @staticmethod
  237. def ucs(ux: 'Vertex', uy: 'Vertex', uz: 'Vertex') -> 'Matrix44':
  238. """
  239. Returns a matrix for coordinate transformation from WCS to UCS.
  240. Origin of both systems is (0, 0, 0).
  241. For transformation from UCS to WCS, transpose the returned matrix.
  242. All vectors as (x, y, z) tuples.
  243. Args:
  244. ux: x-axis for UCS as unit vector
  245. uy: y-axis for UCS as unit vector
  246. uz: z-axis for UCS as unit vector
  247. Returns: Matrix44() object.
  248. """
  249. ux_x, ux_y, ux_z = ux
  250. uy_x, uy_y, uy_z = uy
  251. uz_x, uz_y, uz_z = uz
  252. return Matrix44((
  253. ux_x, uy_x, uz_x, 0,
  254. ux_y, uy_y, uz_y, 0,
  255. ux_z, uy_z, uz_z, 0,
  256. 0, 0, 0, 1,
  257. ))
  258. def __hash__(self) -> int:
  259. return self.matrix.__hash__()
  260. def __setitem__(self, coord: Tuple[int, int], value: float):
  261. """
  262. Set (row, column) element.
  263. """
  264. row, col = coord
  265. self.matrix[row * 4 + col] = float(value)
  266. def __getitem__(self, coord: Tuple[int, int]):
  267. """
  268. Get (row, column) element.
  269. """
  270. row, col = coord
  271. return self.matrix[row * 4 + col]
  272. def __iter__(self) -> Iterable[float]:
  273. """
  274. Iterates over all matrix values.
  275. """
  276. return iter(self.matrix)
  277. def __mul__(self, other: 'Matrix44') -> 'Matrix44':
  278. """
  279. Returns a new matrix as result of the matrix multiplication with another matrix.
  280. """
  281. res_matrix = self.copy()
  282. res_matrix.__imul__(other)
  283. return res_matrix
  284. def __imul__(self, other: 'Matrix44') -> 'Matrix44':
  285. """
  286. Inplace multiplication with another matrix.
  287. """
  288. m1 = self.matrix
  289. m2 = other.matrix
  290. self.matrix = [
  291. m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + m1[3] * m2[12],
  292. m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + m1[3] * m2[13],
  293. m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + m1[3] * m2[14],
  294. m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3] * m2[15],
  295. m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + m1[7] * m2[12],
  296. m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + m1[7] * m2[13],
  297. m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + m1[7] * m2[14],
  298. m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7] * m2[15],
  299. m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + m1[11] * m2[12],
  300. m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + m1[11] * m2[13],
  301. m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + m1[11] * m2[14],
  302. m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11] * m2[15],
  303. m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + m1[15] * m2[12],
  304. m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + m1[15] * m2[13],
  305. m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + m1[15] * m2[14],
  306. m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + m1[15] * m2[15]
  307. ]
  308. return self
  309. def fast_mul(self, other: 'Matrix44') -> 'Matrix44':
  310. """
  311. Multiplies this matrix with other matrix.
  312. Assumes that both matrices have a right column of (0, 0, 0, 1). This is True for matrices composed of
  313. rotations, translations and scales. fast_mul is approximately 25% quicker than the *= operator.
  314. """
  315. m1 = self.matrix
  316. m2 = other.matrix
  317. self.matrix = [
  318. m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8],
  319. m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9],
  320. m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10],
  321. 0.0,
  322. m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8],
  323. m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9],
  324. m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10],
  325. 0.0,
  326. m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8],
  327. m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9],
  328. m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10],
  329. 0.0,
  330. m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + m2[12],
  331. m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + m2[13],
  332. m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + m2[14],
  333. 1.0
  334. ]
  335. return self
  336. def rows(self) -> Iterable[Tuple4Float]:
  337. """
  338. Iterate over rows as 4-tuples.
  339. """
  340. return (self.get_row(index) for index in (0, 1, 2, 3))
  341. def columns(self) -> Iterable[Tuple4Float]:
  342. """
  343. Iterate over columns as 4-tuples.
  344. """
  345. return (self.get_col(index) for index in (0, 1, 2, 3))
  346. def transform(self, vector: 'Vertex') -> Tuple[float, float, float]:
  347. """
  348. Transforms a 3d vector and return the result as a tuple.
  349. """
  350. m = self.matrix
  351. x, y, z = vector
  352. return (x * m[0] + y * m[4] + z * m[8] + m[12],
  353. x * m[1] + y * m[5] + z * m[9] + m[13],
  354. x * m[2] + y * m[6] + z * m[10] + m[14])
  355. def transform_vectors(self, vectors: Iterable['Vertex']) -> List['Vertex']:
  356. """
  357. Returns a list of transformed vectors.
  358. """
  359. result = []
  360. m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 = self.matrix
  361. for vector in vectors:
  362. x, y, z = vector
  363. result.append((
  364. x * m0 + y * m4 + z * m8 + m12,
  365. x * m1 + y * m5 + z * m9 + m13,
  366. x * m2 + y * m6 + z * m10 + m14
  367. ))
  368. return result
  369. def transpose(self) -> None:
  370. """
  371. Swaps the rows for columns inplace.
  372. """
  373. m00, m01, m02, m03, \
  374. m10, m11, m12, m13, \
  375. m20, m21, m22, m23, \
  376. m30, m31, m32, m33 = self.matrix
  377. self.matrix = [
  378. m00, m10, m20, m30,
  379. m01, m11, m21, m31,
  380. m02, m12, m22, m32,
  381. m03, m13, m23, m33
  382. ]
  383. def get_transpose(self) -> 'Matrix44':
  384. """
  385. Returns a new transposed matrix.
  386. """
  387. matrix = self.copy()
  388. matrix.transpose()
  389. return matrix
  390. def determinant(self) -> float:
  391. e11, e12, e13, e14, \
  392. e21, e22, e23, e24, \
  393. e31, e32, e33, e34, \
  394. e41, e42, e43, e44 = self.matrix
  395. return e11 * e22 * e33 * e44 - e11 * e22 * e34 * e43 + e11 * e23 * e34 * e42 - e11 * e23 * e32 * e44 + \
  396. e11 * e24 * e32 * e43 - e11 * e24 * e33 * e42 - e12 * e23 * e34 * e41 + e12 * e23 * e31 * e44 - \
  397. e12 * e24 * e31 * e43 + e12 * e24 * e33 * e41 - e12 * e21 * e33 * e44 + e12 * e21 * e34 * e43 + \
  398. e13 * e24 * e31 * e42 - e13 * e24 * e32 * e41 + e13 * e21 * e32 * e44 - e13 * e21 * e34 * e42 + \
  399. e13 * e22 * e34 * e41 - e13 * e22 * e31 * e44 - e14 * e21 * e32 * e43 + e14 * e21 * e33 * e42 - \
  400. e14 * e22 * e33 * e41 + e14 * e22 * e31 * e43 - e14 * e23 * e31 * e42 + e14 * e23 * e32 * e41
  401. def inverse(self) -> None:
  402. """
  403. Calculates the inverse of the matrix.
  404. Raises ZeroDivisionError if matrix has no inverse.
  405. """
  406. det = self.determinant()
  407. f = 1. / det # catch ZeroDivisionError by caller
  408. m00, m01, m02, m03, \
  409. m10, m11, m12, m13, \
  410. m20, m21, m22, m23, \
  411. m30, m31, m32, m33 = self.matrix
  412. self.matrix = [
  413. (
  414. m12 * m23 * m31 - m13 * m22 * m31 + m13 * m21 * m32 - m11 * m23 * m32 - m12 * m21 * m33 + m11 * m22 * m33) * f,
  415. (
  416. m03 * m22 * m31 - m02 * m23 * m31 - m03 * m21 * m32 + m01 * m23 * m32 + m02 * m21 * m33 - m01 * m22 * m33) * f,
  417. (
  418. m02 * m13 * m31 - m03 * m12 * m31 + m03 * m11 * m32 - m01 * m13 * m32 - m02 * m11 * m33 + m01 * m12 * m33) * f,
  419. (
  420. m03 * m12 * m21 - m02 * m13 * m21 - m03 * m11 * m22 + m01 * m13 * m22 + m02 * m11 * m23 - m01 * m12 * m23) * f,
  421. (
  422. m13 * m22 * m30 - m12 * m23 * m30 - m13 * m20 * m32 + m10 * m23 * m32 + m12 * m20 * m33 - m10 * m22 * m33) * f,
  423. (
  424. m02 * m23 * m30 - m03 * m22 * m30 + m03 * m20 * m32 - m00 * m23 * m32 - m02 * m20 * m33 + m00 * m22 * m33) * f,
  425. (
  426. m03 * m12 * m30 - m02 * m13 * m30 - m03 * m10 * m32 + m00 * m13 * m32 + m02 * m10 * m33 - m00 * m12 * m33) * f,
  427. (
  428. m02 * m13 * m20 - m03 * m12 * m20 + m03 * m10 * m22 - m00 * m13 * m22 - m02 * m10 * m23 + m00 * m12 * m23) * f,
  429. (
  430. m11 * m23 * m30 - m13 * m21 * m30 + m13 * m20 * m31 - m10 * m23 * m31 - m11 * m20 * m33 + m10 * m21 * m33) * f,
  431. (
  432. m03 * m21 * m30 - m01 * m23 * m30 - m03 * m20 * m31 + m00 * m23 * m31 + m01 * m20 * m33 - m00 * m21 * m33) * f,
  433. (
  434. m01 * m13 * m30 - m03 * m11 * m30 + m03 * m10 * m31 - m00 * m13 * m31 - m01 * m10 * m33 + m00 * m11 * m33) * f,
  435. (
  436. m03 * m11 * m20 - m01 * m13 * m20 - m03 * m10 * m21 + m00 * m13 * m21 + m01 * m10 * m23 - m00 * m11 * m23) * f,
  437. (
  438. m12 * m21 * m30 - m11 * m22 * m30 - m12 * m20 * m31 + m10 * m22 * m31 + m11 * m20 * m32 - m10 * m21 * m32) * f,
  439. (
  440. m01 * m22 * m30 - m02 * m21 * m30 + m02 * m20 * m31 - m00 * m22 * m31 - m01 * m20 * m32 + m00 * m21 * m32) * f,
  441. (
  442. m02 * m11 * m30 - m01 * m12 * m30 - m02 * m10 * m31 + m00 * m12 * m31 + m01 * m10 * m32 - m00 * m11 * m32) * f,
  443. (
  444. m01 * m12 * m20 - m02 * m11 * m20 + m02 * m10 * m21 - m00 * m12 * m21 - m01 * m10 * m22 + m00 * m11 * m22) * f,
  445. ]