MatrixMath.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @noflow
  9. */
  10. 'use strict';
  11. const invariant = require('invariant');
  12. /**
  13. * Memory conservative (mutative) matrix math utilities. Uses "command"
  14. * matrices, which are reusable.
  15. */
  16. const MatrixMath = {
  17. createIdentityMatrix: function() {
  18. return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
  19. },
  20. createCopy: function(m) {
  21. return [
  22. m[0],
  23. m[1],
  24. m[2],
  25. m[3],
  26. m[4],
  27. m[5],
  28. m[6],
  29. m[7],
  30. m[8],
  31. m[9],
  32. m[10],
  33. m[11],
  34. m[12],
  35. m[13],
  36. m[14],
  37. m[15],
  38. ];
  39. },
  40. createOrthographic: function(left, right, bottom, top, near, far) {
  41. const a = 2 / (right - left);
  42. const b = 2 / (top - bottom);
  43. const c = -2 / (far - near);
  44. const tx = -(right + left) / (right - left);
  45. const ty = -(top + bottom) / (top - bottom);
  46. const tz = -(far + near) / (far - near);
  47. return [a, 0, 0, 0, 0, b, 0, 0, 0, 0, c, 0, tx, ty, tz, 1];
  48. },
  49. createFrustum: function(left, right, bottom, top, near, far) {
  50. const r_width = 1 / (right - left);
  51. const r_height = 1 / (top - bottom);
  52. const r_depth = 1 / (near - far);
  53. const x = 2 * (near * r_width);
  54. const y = 2 * (near * r_height);
  55. const A = (right + left) * r_width;
  56. const B = (top + bottom) * r_height;
  57. const C = (far + near) * r_depth;
  58. const D = 2 * (far * near * r_depth);
  59. return [x, 0, 0, 0, 0, y, 0, 0, A, B, C, -1, 0, 0, D, 0];
  60. },
  61. /**
  62. * This create a perspective projection towards negative z
  63. * Clipping the z range of [-near, -far]
  64. *
  65. * @param fovInRadians - field of view in randians
  66. */
  67. createPerspective: function(fovInRadians, aspect, near, far) {
  68. const h = 1 / Math.tan(fovInRadians / 2);
  69. const r_depth = 1 / (near - far);
  70. const C = (far + near) * r_depth;
  71. const D = 2 * (far * near * r_depth);
  72. return [h / aspect, 0, 0, 0, 0, h, 0, 0, 0, 0, C, -1, 0, 0, D, 0];
  73. },
  74. createTranslate2d: function(x, y) {
  75. const mat = MatrixMath.createIdentityMatrix();
  76. MatrixMath.reuseTranslate2dCommand(mat, x, y);
  77. return mat;
  78. },
  79. reuseTranslate2dCommand: function(matrixCommand, x, y) {
  80. matrixCommand[12] = x;
  81. matrixCommand[13] = y;
  82. },
  83. reuseTranslate3dCommand: function(matrixCommand, x, y, z) {
  84. matrixCommand[12] = x;
  85. matrixCommand[13] = y;
  86. matrixCommand[14] = z;
  87. },
  88. createScale: function(factor) {
  89. const mat = MatrixMath.createIdentityMatrix();
  90. MatrixMath.reuseScaleCommand(mat, factor);
  91. return mat;
  92. },
  93. reuseScaleCommand: function(matrixCommand, factor) {
  94. matrixCommand[0] = factor;
  95. matrixCommand[5] = factor;
  96. },
  97. reuseScale3dCommand: function(matrixCommand, x, y, z) {
  98. matrixCommand[0] = x;
  99. matrixCommand[5] = y;
  100. matrixCommand[10] = z;
  101. },
  102. reusePerspectiveCommand: function(matrixCommand, p) {
  103. matrixCommand[11] = -1 / p;
  104. },
  105. reuseScaleXCommand(matrixCommand, factor) {
  106. matrixCommand[0] = factor;
  107. },
  108. reuseScaleYCommand(matrixCommand, factor) {
  109. matrixCommand[5] = factor;
  110. },
  111. reuseScaleZCommand(matrixCommand, factor) {
  112. matrixCommand[10] = factor;
  113. },
  114. reuseRotateXCommand: function(matrixCommand, radians) {
  115. matrixCommand[5] = Math.cos(radians);
  116. matrixCommand[6] = Math.sin(radians);
  117. matrixCommand[9] = -Math.sin(radians);
  118. matrixCommand[10] = Math.cos(radians);
  119. },
  120. reuseRotateYCommand: function(matrixCommand, amount) {
  121. matrixCommand[0] = Math.cos(amount);
  122. matrixCommand[2] = -Math.sin(amount);
  123. matrixCommand[8] = Math.sin(amount);
  124. matrixCommand[10] = Math.cos(amount);
  125. },
  126. // http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix
  127. reuseRotateZCommand: function(matrixCommand, radians) {
  128. matrixCommand[0] = Math.cos(radians);
  129. matrixCommand[1] = Math.sin(radians);
  130. matrixCommand[4] = -Math.sin(radians);
  131. matrixCommand[5] = Math.cos(radians);
  132. },
  133. createRotateZ: function(radians) {
  134. const mat = MatrixMath.createIdentityMatrix();
  135. MatrixMath.reuseRotateZCommand(mat, radians);
  136. return mat;
  137. },
  138. reuseSkewXCommand: function(matrixCommand, radians) {
  139. matrixCommand[4] = Math.tan(radians);
  140. },
  141. reuseSkewYCommand: function(matrixCommand, radians) {
  142. matrixCommand[1] = Math.tan(radians);
  143. },
  144. multiplyInto: function(out, a, b) {
  145. const a00 = a[0],
  146. a01 = a[1],
  147. a02 = a[2],
  148. a03 = a[3],
  149. a10 = a[4],
  150. a11 = a[5],
  151. a12 = a[6],
  152. a13 = a[7],
  153. a20 = a[8],
  154. a21 = a[9],
  155. a22 = a[10],
  156. a23 = a[11],
  157. a30 = a[12],
  158. a31 = a[13],
  159. a32 = a[14],
  160. a33 = a[15];
  161. let b0 = b[0],
  162. b1 = b[1],
  163. b2 = b[2],
  164. b3 = b[3];
  165. out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
  166. out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
  167. out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
  168. out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
  169. b0 = b[4];
  170. b1 = b[5];
  171. b2 = b[6];
  172. b3 = b[7];
  173. out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
  174. out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
  175. out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
  176. out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
  177. b0 = b[8];
  178. b1 = b[9];
  179. b2 = b[10];
  180. b3 = b[11];
  181. out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
  182. out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
  183. out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
  184. out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
  185. b0 = b[12];
  186. b1 = b[13];
  187. b2 = b[14];
  188. b3 = b[15];
  189. out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
  190. out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
  191. out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
  192. out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
  193. },
  194. determinant(matrix: Array<number>): number {
  195. const [
  196. m00,
  197. m01,
  198. m02,
  199. m03,
  200. m10,
  201. m11,
  202. m12,
  203. m13,
  204. m20,
  205. m21,
  206. m22,
  207. m23,
  208. m30,
  209. m31,
  210. m32,
  211. m33,
  212. ] = matrix;
  213. return (
  214. m03 * m12 * m21 * m30 -
  215. m02 * m13 * m21 * m30 -
  216. m03 * m11 * m22 * m30 +
  217. m01 * m13 * m22 * m30 +
  218. m02 * m11 * m23 * m30 -
  219. m01 * m12 * m23 * m30 -
  220. m03 * m12 * m20 * m31 +
  221. m02 * m13 * m20 * m31 +
  222. m03 * m10 * m22 * m31 -
  223. m00 * m13 * m22 * m31 -
  224. m02 * m10 * m23 * m31 +
  225. m00 * m12 * m23 * m31 +
  226. m03 * m11 * m20 * m32 -
  227. m01 * m13 * m20 * m32 -
  228. m03 * m10 * m21 * m32 +
  229. m00 * m13 * m21 * m32 +
  230. m01 * m10 * m23 * m32 -
  231. m00 * m11 * m23 * m32 -
  232. m02 * m11 * m20 * m33 +
  233. m01 * m12 * m20 * m33 +
  234. m02 * m10 * m21 * m33 -
  235. m00 * m12 * m21 * m33 -
  236. m01 * m10 * m22 * m33 +
  237. m00 * m11 * m22 * m33
  238. );
  239. },
  240. /**
  241. * Inverse of a matrix. Multiplying by the inverse is used in matrix math
  242. * instead of division.
  243. *
  244. * Formula from:
  245. * http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
  246. */
  247. inverse(matrix: Array<number>): Array<number> {
  248. const det = MatrixMath.determinant(matrix);
  249. if (!det) {
  250. return matrix;
  251. }
  252. const [
  253. m00,
  254. m01,
  255. m02,
  256. m03,
  257. m10,
  258. m11,
  259. m12,
  260. m13,
  261. m20,
  262. m21,
  263. m22,
  264. m23,
  265. m30,
  266. m31,
  267. m32,
  268. m33,
  269. ] = matrix;
  270. return [
  271. (m12 * m23 * m31 -
  272. m13 * m22 * m31 +
  273. m13 * m21 * m32 -
  274. m11 * m23 * m32 -
  275. m12 * m21 * m33 +
  276. m11 * m22 * m33) /
  277. det,
  278. (m03 * m22 * m31 -
  279. m02 * m23 * m31 -
  280. m03 * m21 * m32 +
  281. m01 * m23 * m32 +
  282. m02 * m21 * m33 -
  283. m01 * m22 * m33) /
  284. det,
  285. (m02 * m13 * m31 -
  286. m03 * m12 * m31 +
  287. m03 * m11 * m32 -
  288. m01 * m13 * m32 -
  289. m02 * m11 * m33 +
  290. m01 * m12 * m33) /
  291. det,
  292. (m03 * m12 * m21 -
  293. m02 * m13 * m21 -
  294. m03 * m11 * m22 +
  295. m01 * m13 * m22 +
  296. m02 * m11 * m23 -
  297. m01 * m12 * m23) /
  298. det,
  299. (m13 * m22 * m30 -
  300. m12 * m23 * m30 -
  301. m13 * m20 * m32 +
  302. m10 * m23 * m32 +
  303. m12 * m20 * m33 -
  304. m10 * m22 * m33) /
  305. det,
  306. (m02 * m23 * m30 -
  307. m03 * m22 * m30 +
  308. m03 * m20 * m32 -
  309. m00 * m23 * m32 -
  310. m02 * m20 * m33 +
  311. m00 * m22 * m33) /
  312. det,
  313. (m03 * m12 * m30 -
  314. m02 * m13 * m30 -
  315. m03 * m10 * m32 +
  316. m00 * m13 * m32 +
  317. m02 * m10 * m33 -
  318. m00 * m12 * m33) /
  319. det,
  320. (m02 * m13 * m20 -
  321. m03 * m12 * m20 +
  322. m03 * m10 * m22 -
  323. m00 * m13 * m22 -
  324. m02 * m10 * m23 +
  325. m00 * m12 * m23) /
  326. det,
  327. (m11 * m23 * m30 -
  328. m13 * m21 * m30 +
  329. m13 * m20 * m31 -
  330. m10 * m23 * m31 -
  331. m11 * m20 * m33 +
  332. m10 * m21 * m33) /
  333. det,
  334. (m03 * m21 * m30 -
  335. m01 * m23 * m30 -
  336. m03 * m20 * m31 +
  337. m00 * m23 * m31 +
  338. m01 * m20 * m33 -
  339. m00 * m21 * m33) /
  340. det,
  341. (m01 * m13 * m30 -
  342. m03 * m11 * m30 +
  343. m03 * m10 * m31 -
  344. m00 * m13 * m31 -
  345. m01 * m10 * m33 +
  346. m00 * m11 * m33) /
  347. det,
  348. (m03 * m11 * m20 -
  349. m01 * m13 * m20 -
  350. m03 * m10 * m21 +
  351. m00 * m13 * m21 +
  352. m01 * m10 * m23 -
  353. m00 * m11 * m23) /
  354. det,
  355. (m12 * m21 * m30 -
  356. m11 * m22 * m30 -
  357. m12 * m20 * m31 +
  358. m10 * m22 * m31 +
  359. m11 * m20 * m32 -
  360. m10 * m21 * m32) /
  361. det,
  362. (m01 * m22 * m30 -
  363. m02 * m21 * m30 +
  364. m02 * m20 * m31 -
  365. m00 * m22 * m31 -
  366. m01 * m20 * m32 +
  367. m00 * m21 * m32) /
  368. det,
  369. (m02 * m11 * m30 -
  370. m01 * m12 * m30 -
  371. m02 * m10 * m31 +
  372. m00 * m12 * m31 +
  373. m01 * m10 * m32 -
  374. m00 * m11 * m32) /
  375. det,
  376. (m01 * m12 * m20 -
  377. m02 * m11 * m20 +
  378. m02 * m10 * m21 -
  379. m00 * m12 * m21 -
  380. m01 * m10 * m22 +
  381. m00 * m11 * m22) /
  382. det,
  383. ];
  384. },
  385. /**
  386. * Turns columns into rows and rows into columns.
  387. */
  388. transpose(m: Array<number>): Array<number> {
  389. return [
  390. m[0],
  391. m[4],
  392. m[8],
  393. m[12],
  394. m[1],
  395. m[5],
  396. m[9],
  397. m[13],
  398. m[2],
  399. m[6],
  400. m[10],
  401. m[14],
  402. m[3],
  403. m[7],
  404. m[11],
  405. m[15],
  406. ];
  407. },
  408. /**
  409. * Based on: http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c
  410. */
  411. multiplyVectorByMatrix(v: Array<number>, m: Array<number>): Array<number> {
  412. const [vx, vy, vz, vw] = v;
  413. return [
  414. vx * m[0] + vy * m[4] + vz * m[8] + vw * m[12],
  415. vx * m[1] + vy * m[5] + vz * m[9] + vw * m[13],
  416. vx * m[2] + vy * m[6] + vz * m[10] + vw * m[14],
  417. vx * m[3] + vy * m[7] + vz * m[11] + vw * m[15],
  418. ];
  419. },
  420. /**
  421. * From: https://code.google.com/p/webgl-mjs/source/browse/mjs.js
  422. */
  423. v3Length(a: Array<number>): number {
  424. return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
  425. },
  426. /**
  427. * Based on: https://code.google.com/p/webgl-mjs/source/browse/mjs.js
  428. */
  429. v3Normalize(vector: Array<number>, v3Length: number): Array<number> {
  430. const im = 1 / (v3Length || MatrixMath.v3Length(vector));
  431. return [vector[0] * im, vector[1] * im, vector[2] * im];
  432. },
  433. /**
  434. * The dot product of a and b, two 3-element vectors.
  435. * From: https://code.google.com/p/webgl-mjs/source/browse/mjs.js
  436. */
  437. v3Dot(a, b) {
  438. return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  439. },
  440. /**
  441. * From:
  442. * http://www.opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp
  443. */
  444. v3Combine(
  445. a: Array<number>,
  446. b: Array<number>,
  447. aScale: number,
  448. bScale: number,
  449. ): Array<number> {
  450. return [
  451. aScale * a[0] + bScale * b[0],
  452. aScale * a[1] + bScale * b[1],
  453. aScale * a[2] + bScale * b[2],
  454. ];
  455. },
  456. /**
  457. * From:
  458. * http://www.opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp
  459. */
  460. v3Cross(a: Array<number>, b: Array<number>): Array<number> {
  461. return [
  462. a[1] * b[2] - a[2] * b[1],
  463. a[2] * b[0] - a[0] * b[2],
  464. a[0] * b[1] - a[1] * b[0],
  465. ];
  466. },
  467. /**
  468. * Based on:
  469. * http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
  470. * and:
  471. * http://quat.zachbennett.com/
  472. *
  473. * Note that this rounds degrees to the thousandth of a degree, due to
  474. * floating point errors in the creation of the quaternion.
  475. *
  476. * Also note that this expects the qw value to be last, not first.
  477. *
  478. * Also, when researching this, remember that:
  479. * yaw === heading === z-axis
  480. * pitch === elevation/attitude === y-axis
  481. * roll === bank === x-axis
  482. */
  483. quaternionToDegreesXYZ(q: Array<number>, matrix, row): Array<number> {
  484. const [qx, qy, qz, qw] = q;
  485. const qw2 = qw * qw;
  486. const qx2 = qx * qx;
  487. const qy2 = qy * qy;
  488. const qz2 = qz * qz;
  489. const test = qx * qy + qz * qw;
  490. const unit = qw2 + qx2 + qy2 + qz2;
  491. const conv = 180 / Math.PI;
  492. if (test > 0.49999 * unit) {
  493. return [0, 2 * Math.atan2(qx, qw) * conv, 90];
  494. }
  495. if (test < -0.49999 * unit) {
  496. return [0, -2 * Math.atan2(qx, qw) * conv, -90];
  497. }
  498. return [
  499. MatrixMath.roundTo3Places(
  500. Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx2 - 2 * qz2) * conv,
  501. ),
  502. MatrixMath.roundTo3Places(
  503. Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy2 - 2 * qz2) * conv,
  504. ),
  505. MatrixMath.roundTo3Places(Math.asin(2 * qx * qy + 2 * qz * qw) * conv),
  506. ];
  507. },
  508. /**
  509. * Based on:
  510. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
  511. */
  512. roundTo3Places(n: number): number {
  513. const arr = n.toString().split('e');
  514. return Math.round(arr[0] + 'e' + (arr[1] ? +arr[1] - 3 : 3)) * 0.001;
  515. },
  516. /**
  517. * Decompose a matrix into separate transform values, for use on platforms
  518. * where applying a precomposed matrix is not possible, and transforms are
  519. * applied in an inflexible ordering (e.g. Android).
  520. *
  521. * Implementation based on
  522. * http://www.w3.org/TR/css3-transforms/#decomposing-a-2d-matrix
  523. * http://www.w3.org/TR/css3-transforms/#decomposing-a-3d-matrix
  524. * which was based on
  525. * http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c
  526. */
  527. decomposeMatrix(transformMatrix: Array<number>): ?Object {
  528. invariant(
  529. transformMatrix.length === 16,
  530. 'Matrix decomposition needs a list of 3d matrix values, received %s',
  531. transformMatrix,
  532. );
  533. // output values
  534. let perspective = [];
  535. const quaternion = [];
  536. const scale = [];
  537. const skew = [];
  538. const translation = [];
  539. // create normalized, 2d array matrix
  540. // and normalized 1d array perspectiveMatrix with redefined 4th column
  541. if (!transformMatrix[15]) {
  542. return;
  543. }
  544. const matrix = [];
  545. const perspectiveMatrix = [];
  546. for (let i = 0; i < 4; i++) {
  547. matrix.push([]);
  548. for (let j = 0; j < 4; j++) {
  549. const value = transformMatrix[i * 4 + j] / transformMatrix[15];
  550. matrix[i].push(value);
  551. perspectiveMatrix.push(j === 3 ? 0 : value);
  552. }
  553. }
  554. perspectiveMatrix[15] = 1;
  555. // test for singularity of upper 3x3 part of the perspective matrix
  556. if (!MatrixMath.determinant(perspectiveMatrix)) {
  557. return;
  558. }
  559. // isolate perspective
  560. if (matrix[0][3] !== 0 || matrix[1][3] !== 0 || matrix[2][3] !== 0) {
  561. // rightHandSide is the right hand side of the equation.
  562. // rightHandSide is a vector, or point in 3d space relative to the origin.
  563. const rightHandSide = [
  564. matrix[0][3],
  565. matrix[1][3],
  566. matrix[2][3],
  567. matrix[3][3],
  568. ];
  569. // Solve the equation by inverting perspectiveMatrix and multiplying
  570. // rightHandSide by the inverse.
  571. const inversePerspectiveMatrix = MatrixMath.inverse(perspectiveMatrix);
  572. const transposedInversePerspectiveMatrix = MatrixMath.transpose(
  573. inversePerspectiveMatrix,
  574. );
  575. perspective = MatrixMath.multiplyVectorByMatrix(
  576. rightHandSide,
  577. transposedInversePerspectiveMatrix,
  578. );
  579. } else {
  580. // no perspective
  581. perspective[0] = perspective[1] = perspective[2] = 0;
  582. perspective[3] = 1;
  583. }
  584. // translation is simple
  585. for (let i = 0; i < 3; i++) {
  586. translation[i] = matrix[3][i];
  587. }
  588. // Now get scale and shear.
  589. // 'row' is a 3 element array of 3 component vectors
  590. const row = [];
  591. for (let i = 0; i < 3; i++) {
  592. row[i] = [matrix[i][0], matrix[i][1], matrix[i][2]];
  593. }
  594. // Compute X scale factor and normalize first row.
  595. scale[0] = MatrixMath.v3Length(row[0]);
  596. row[0] = MatrixMath.v3Normalize(row[0], scale[0]);
  597. // Compute XY shear factor and make 2nd row orthogonal to 1st.
  598. skew[0] = MatrixMath.v3Dot(row[0], row[1]);
  599. row[1] = MatrixMath.v3Combine(row[1], row[0], 1.0, -skew[0]);
  600. // Now, compute Y scale and normalize 2nd row.
  601. scale[1] = MatrixMath.v3Length(row[1]);
  602. row[1] = MatrixMath.v3Normalize(row[1], scale[1]);
  603. skew[0] /= scale[1];
  604. // Compute XZ and YZ shears, orthogonalize 3rd row
  605. skew[1] = MatrixMath.v3Dot(row[0], row[2]);
  606. row[2] = MatrixMath.v3Combine(row[2], row[0], 1.0, -skew[1]);
  607. skew[2] = MatrixMath.v3Dot(row[1], row[2]);
  608. row[2] = MatrixMath.v3Combine(row[2], row[1], 1.0, -skew[2]);
  609. // Next, get Z scale and normalize 3rd row.
  610. scale[2] = MatrixMath.v3Length(row[2]);
  611. row[2] = MatrixMath.v3Normalize(row[2], scale[2]);
  612. skew[1] /= scale[2];
  613. skew[2] /= scale[2];
  614. // At this point, the matrix (in rows) is orthonormal.
  615. // Check for a coordinate system flip. If the determinant
  616. // is -1, then negate the matrix and the scaling factors.
  617. const pdum3 = MatrixMath.v3Cross(row[1], row[2]);
  618. if (MatrixMath.v3Dot(row[0], pdum3) < 0) {
  619. for (let i = 0; i < 3; i++) {
  620. scale[i] *= -1;
  621. row[i][0] *= -1;
  622. row[i][1] *= -1;
  623. row[i][2] *= -1;
  624. }
  625. }
  626. // Now, get the rotations out
  627. quaternion[0] =
  628. 0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0));
  629. quaternion[1] =
  630. 0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0));
  631. quaternion[2] =
  632. 0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0));
  633. quaternion[3] =
  634. 0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0));
  635. if (row[2][1] > row[1][2]) {
  636. quaternion[0] = -quaternion[0];
  637. }
  638. if (row[0][2] > row[2][0]) {
  639. quaternion[1] = -quaternion[1];
  640. }
  641. if (row[1][0] > row[0][1]) {
  642. quaternion[2] = -quaternion[2];
  643. }
  644. // correct for occasional, weird Euler synonyms for 2d rotation
  645. let rotationDegrees;
  646. if (
  647. quaternion[0] < 0.001 &&
  648. quaternion[0] >= 0 &&
  649. quaternion[1] < 0.001 &&
  650. quaternion[1] >= 0
  651. ) {
  652. // this is a 2d rotation on the z-axis
  653. rotationDegrees = [
  654. 0,
  655. 0,
  656. MatrixMath.roundTo3Places(
  657. (Math.atan2(row[0][1], row[0][0]) * 180) / Math.PI,
  658. ),
  659. ];
  660. } else {
  661. rotationDegrees = MatrixMath.quaternionToDegreesXYZ(
  662. quaternion,
  663. matrix,
  664. row,
  665. );
  666. }
  667. // expose both base data and convenience names
  668. return {
  669. rotationDegrees,
  670. perspective,
  671. quaternion,
  672. scale,
  673. skew,
  674. translation,
  675. rotate: rotationDegrees[2],
  676. rotateX: rotationDegrees[0],
  677. rotateY: rotationDegrees[1],
  678. scaleX: scale[0],
  679. scaleY: scale[1],
  680. translateX: translation[0],
  681. translateY: translation[1],
  682. };
  683. },
  684. };
  685. module.exports = MatrixMath;